OCLCollections.java

package org.andromda.translation.ocl.validation;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;
import org.apache.commons.collections.Bag;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.collections.SetUtils;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.bag.HashBag;
import org.apache.commons.lang.StringUtils;

/**
 * Used to translated OCL collection expressions to their corresponding Java collection expressions.
 */
public final class OCLCollections
{
    /**
     * Counts the number of occurrences of the argument item in the source collection.
     * @param collection
     * @param item
     * @return CollectionUtils.cardinality(item, collection)
     */
    public static int count(
            final Collection collection,
            Object item)
    {
        return collection == null ? 0 : CollectionUtils.cardinality(item, collection);
    }

    /**
     * Return true if the object is not an element of the collection, false otherwise.
     * @param collection
     * @param item
     * @return !collection.contains(item
     */
    public static boolean excludes(
            final Collection collection,
            final Object item)
    {
        return collection == null || !collection.contains(item);
    }

    /**
     * Returns true if all elements of the parameter collection are not present in the current collection, false
     * otherwise.
     * @param collection
     * @param items
     * @return excludesAll
     */
    public static boolean excludesAll(
            final Collection collection,
            final Collection items)
    {
        boolean excludesAll = true;
        for (final Iterator iterator = items.iterator(); iterator.hasNext();)
        {
            final Object object = iterator.next();
            if (!excludes(collection, object))
            {
                excludesAll = false;
                break;
            }
        }
        return excludesAll;
    }

    /**
     * Returns true if the object is an element of the collection, false otherwise.
     * @param collection
     * @param item
     * @return collection.contains(item)
     */
    public static boolean includes(
            final Collection collection,
            final Object item)
    {
        return collection != null && collection.contains(item);
    }

    /**
     * Returns true if all elements of the parameter collection are present in the current collection, false otherwise.
     * @param collection
     * @param items
     * @return collection.containsAll(items)
     */
    public static boolean includesAll(
            final Collection collection,
            final Collection items)
    {
        return collection != null && collection.containsAll(items);
    }

    /**
     * Returns true if the collection contains no elements, false otherwise.
     * @param collection
     * @return collection.isEmpty()
     */
    public static boolean isEmpty(final Collection collection)
    {
        return (collection == null) || (collection.isEmpty());
    }

    /**
     * Returns true if the argument is <code>null</code>, false otherwise.
     * @param object
     * @return isEmpty
     */
    public static boolean isEmpty(final Object object)
    {
        boolean isEmpty = object == null;
        if (!isEmpty)
        {
            if (object instanceof Collection)
            {
                isEmpty = ((Collection) object).isEmpty();
            }
            else if (object instanceof String)
            {
                isEmpty = isEmpty((String) object);
            }
            else if (object != null && object.getClass().isArray())
            {
                isEmpty = ((Object[]) object).length == 0;
            }
        }
        return isEmpty;
    }

    /**
     * Returns true if the argument is either <code>null</code> or only contains whitespace characters, false
     * otherwise.
     * @param string
     * @return isBlank(string)
     */
    public static boolean isEmpty(final String string)
    {
        return StringUtils.isBlank(string);
    }

    /**
     * Returns true if the collection contains one or more elements, false otherwise.
     * @param collection
     * @return isEmpty(collection)
     */
    public static boolean notEmpty(final Collection collection)
    {
        return (collection != null) && !isEmpty(collection);
    }

    /**
     * Returns true if the argument is not <code>null</code>, false otherwise.
     * @param object
     * @return notEmpty
     */
    public static boolean notEmpty(final Object object)
    {
        boolean notEmpty = object != null;
        if (notEmpty)
        {
            if (object instanceof Collection)
            {
                notEmpty = !((Collection) object).isEmpty();
            }
            else if (object instanceof String)
            {
                notEmpty = notEmpty((String) object);
            }
            else if (object != null && object.getClass().isArray())
            {
                notEmpty = ((Object[]) object).length > 0;
            }
        }
        return notEmpty;
    }

    /**
     * Returns true if the argument is neither <code>null</code> nor only contains whitespace characters, false
     * otherwise.
     * @param string
     * @return isNotBlank(string)
     */
    public static boolean notEmpty(final String string)
    {
        return StringUtils.isNotBlank(string);
    }

    /**
     * Checks the instance of the <code>object</code> and makes sure its a Collection, if the object is a collection the
     * size is checked and returned, if its NOT a collection, 0 is returned.
     *
     * @param object the object to check.
     * @return the size of the collection
     */
    public static int size(final Object object)
    {
        int size = 0;
        if (object != null)
        {
            if (object instanceof Collection)
            {
                size = size((Collection) object);
            } else if (object.getClass().isArray())
            {
                size = ((Object[]) object).length;
            }
        }
        return size;
    }

    /**
     * Returns the number of elements in the collection.
     * @param collection
     * @return collection.size()
     */
    public static int size(final Collection collection)
    {
        int size = 0;
        if (collection != null)
        {
            size = collection.size();
        }
        return size;
    }

    /**
     * Returns the sum of all the elements in the collection. Every element must extend Number or this method
     * will throw an exception.
     *
     * @param collection a collection containing only classes extending Number
     * @return the sum of all the elements in the collection
     */
    public static double sum(final Object collection)
    {
        double sum = 0;
        if (collection != null)
        {
            if (collection instanceof Collection)
            {
                // TODO Fix infinite loop
                sum = sum(collection);
            }
            else if (collection.getClass().isArray())
            {
                sum = sum(Arrays.asList((Object[]) collection));
            }
        }
        return sum;
    }

    /**
     * Returns the sum of all the element in the collection. Every element must extend Number or this method
     * will throw an exception.
     *
     * @param collection a collection containing only classes extending Number
     * @return the sum of all the elements in the collection
     */
    public static double sum(final Collection collection)
    {
        double sum = 0;
        if (collection != null && !collection.isEmpty())
        {
            for (final Iterator iterator = collection.iterator(); iterator.hasNext();)
            {
                Object object = iterator.next();
                if (object instanceof Number)
                {
                    sum += ((Number) object).doubleValue();
                } else
                {
                    throw new UnsupportedOperationException(
                            "In order to calculate the sum of a collection\'s elements " +
                                    "all of them must extend Number, found: " + object.getClass().getName());
                }
            }
        }
        return sum;
    }

    /**
     * Appends the item to the list.
     * @param list
     * @param item
     * @return true if the operation was a success
     */
    public static boolean append(
            final List list,
            final Object item)
    {
        return list == null ? false : list.add(item);
    }

    /**
     * Insert the item into the first position of the list.
     * @param list
     * @param item
     * @return the element previously at the first position
     */
    public static Object prepend(
            final List list,
            final Object item)
    {
        return list.set(0, item);
    }

    /**
     * Appends the item to the bag.
     * @param collection
     * @param item
     * @return true if the operation was a success
     */
    public static boolean append(
            final Bag collection,
            final Object item)
    {
        return collection == null ? false : collection.add(item);
    }

    /**
     * Returns the argument as a bag.
     * @param collection
     * @return new HashBag(collection)
     */
    public static Bag asBag(final Collection collection)
    {
        return collection == null ? new HashBag() : new HashBag(collection);
    }

    /**
     * Returns the argument as an ordered set.
     * @param collection
     * @return SetUtils.orderedSet(new TreeSet(collection))
     */
    public static Set asOrderedSet(final Collection collection)
    {
        return collection == null ? Collections.emptySet() : SetUtils.orderedSet(new TreeSet(collection));
    }

    /**
     * Returns the argument as a list.
     * @param collection
     * @return new ArrayList(collection)
     */
    public static List asSequence(final Collection collection)
    {
        return collection == null ? Collections.emptyList() : new ArrayList(collection);
    }

    /**
     * Returns the argument as a set.
     * @param collection
     * @return new HashSet(collection)
     */
    public static Set asSet(final Collection collection)
    {
        return collection == null ? Collections.emptySet() : new HashSet(collection);
    }

    /**
     * Returns the element at the specified index in the argument list.
     * @param list
     * @param index
     * @return list.get(index)
     */
    public static Object at(
            final List list,
            final int index)
    {
        return list == null ? null : list.get(index);
    }

    /**
     * Removes all occurrences of the item in the source collection.
     * @param collection
     * @param item
     * @return true if one or more elements were removed
     */
    public static boolean excluding(
            final Collection collection,
            final Object item)
    {
        return collection == null ? false : collection.remove(item);
    }

    /**
     * Adds the item to the list
     * @param collection
     * @param item
     * @return true if the element was added
     */
    public static boolean including(
            final Collection collection,
            final Object item)
    {
        return collection == null ? false : collection.add(item);
    }

    /**
     * Recursively flattens this collection, this method returns a Collection containing no nested Collection
     * instances.
     * @param collection
     * @return flattenedCollection.addAll(flatten((Collection) object))
     */
    public static Collection flatten(final Collection collection)
    {
        final Collection flattenedCollection = new ArrayList();
        for (final Iterator iterator = collection.iterator(); iterator.hasNext();)
        {
            final Object object = iterator.next();
            if (object instanceof Collection)
            {
                flattenedCollection.addAll(flatten((Collection) object));
            }
            else
            {
                flattenedCollection.add(object);
            }
        }

        return flattenedCollection;
    }

    /**
     * Returns the index in this list of the first occurrence of the specified element, or -1 if this list does not
     * contain this element. More formally, returns the lowest index i such that (o == null ? get(i) = =null :
     * o.equals(get(i))), or -1 if there is no such index.
     * @param collection
     * @param item
     * @return collection.indexOf(item)
     */
    public static int indexOf(
            final List collection,
            final Object item)
    {
        return collection == null ? -1 : collection.indexOf(item);
    }

    /**
     * Insert the item at the specified index into the collection.
     * @param collection
     * @param index
     * @param item
     */
    public static void insertAt(
            final List collection,
            int index,
            Object item)
    {
        collection.add(index, item);
    }

    /**
     * Returns the collection of elements common in both argument collections.
     * @param first
     * @param second
     * @return CollectionUtils.intersection(first, second)
     */
    public static Collection intersection(
            final Collection first,
            final Collection second)
    {
        return CollectionUtils.intersection(first, second);
    }

    /**
     * Returns the union of both collections into a single collection.
     * @param first
     * @param second
     * @return CollectionUtils.union(first, second)
     */
    public static Collection union(
            final Collection first,
            final Collection second)
    {
        return CollectionUtils.union(first, second);
    }

    /**
     * Returns the last element in the collection.
     *
     * @param object the collection or single instance which will be converted to a collection.
     * @return the last object of the collection or the object itself if the object is not a collection instance (or
     *         null if the object is null or an empty collection).
     */
    public static Object last(final Object object)
    {
        Object last = null;
        final List list = objectToList(object);
        if (!list.isEmpty())
        {
            last = list.get(list.size() - 1);
        }
        return last;
    }

    /**
     * Returns the first element in the collection.
     *
     * @param object the collection or single instance which will be converted to a collection.
     * @return the first object of the collection or the object itself if the object is not a collection instance (or
     *         null if the object is null or an empty collection).
     */
    public static Object first(final Object object)
    {
        Object first = null;
        final List list = objectToList(object);
        if (!list.isEmpty())
        {
            first = list.get(0);
        }
        return first;
    }

    /**
     * Returns those element that are contained in only one of both collections.
     * @param first
     * @param second
     * @return CollectionUtils.disjunction(first, second)
     */
    public static Collection symmetricDifference(
            final Collection first,
            final Collection second)
    {
        return CollectionUtils.disjunction(first, second);
    }

    /**
     * TODO: implement
     * @param collection
     * @return UnsupportedOperationException
     */
    public static Set subOrderedSet(final Set collection)
    {
        throw new UnsupportedOperationException(OCLCollections.class.getName() + ".subOrderedSet");
    }

    /**
     * TODO: implement
     * @param collection
     * @return UnsupportedOperationException
     */
    public static List subSequence(final List collection)
    {
        throw new UnsupportedOperationException(OCLCollections.class.getName() + ".subSequence");
    }

    /**
     * Returns a random element from the collection for which the argument expression evaluates true.
     * @param collection
     * @param predicate
     * @return selectedElements.get(random.nextInt(selectedElements.size())
     */
    public static Object any(
            final Collection collection,
            final Predicate predicate)
    {
        final List selectedElements = new ArrayList(select(collection, predicate));
        final Random random = new Random(System.currentTimeMillis());
        return selectedElements.isEmpty() ? null : selectedElements.get(random.nextInt(selectedElements.size()));
    }

    /**
     * Returns the collection of Objects that results from executing the transformer on each individual element in the
     * source collection.
     * @param collection
     * @param transformer
     * @return CollectionUtils.collect(collection, transformer)
     */
    public static Collection collect(
            final Collection collection,
            final Transformer transformer)
    {
        return CollectionUtils.collect(collection, transformer);
    }

    /**
     * TODO: implement
     * @param collection
     * @return UnsupportedOperationException
     */
    public static Collection collectNested(final Collection collection)
    {
        throw new UnsupportedOperationException(OCLCollections.class.getName() + ".collectNested");
    }

    /**
     * Returns true if a predicate is true for at least one element of a collection. <p/>A null collection or predicate
     * returns false.
     * @param collection
     * @param predicate
     * @return CollectionUtils.exists(collection, predicate)
     */
    public static boolean exists(
            final Collection collection,
            final Predicate predicate)
    {
        return CollectionUtils.exists(collection, predicate);
    }

    /**
     * Returns true if a predicate is true for at least one element of a collection. <p/>A null collection or predicate
     * returns false.
     * @param collection
     * @param predicate
     * @return exists((Collection) collection, predicate)
     */
    public static boolean exists(
            final Object collection,
            final Predicate predicate)
    {
        return collection instanceof Collection ? exists((Collection) collection, predicate) : false;
    }

    /**
     * <p/>
     * Executes every <code>predicate</code> for the given collectoin, if one evaluates to <code>false</code> this
     * operation returns <code>false</code>, otherwise <code>true</code> is returned. </p> If the input collection or
     * closure is null <code>false</code> is returned.
     * @param collection
     * @param predicate
     * @return true if every evaluated predicate returns true, false otherwise.
     */
    public static boolean forAll(
            final Collection collection,
            final Predicate predicate)
    {
        boolean valid = collection != null;
        if (valid && collection != null)
        {
            for (final Iterator iterator = collection.iterator(); iterator.hasNext();)
            {
                final Object object = iterator.next();
                valid = predicate.evaluate(object);
                if (!valid)
                {
                    break;
                }
            }
        }
        return valid;
    }

    /**
     * <p/>
     * Executes every <code>predicate</code> for the given collection, if one evaluates to <code>false</code> this
     * operation returns <code>false</code>, otherwise <code>true</code> is returned. </p> If the input collection or
     * closure is null <code>false</code> is returned.
     * @param collection
     * @param predicate
     *
     * @return true if every evaluated predicate returns true, false otherwise.
     */
    public static boolean forAll(
            final Object collection,
            final Predicate predicate)
    {
        boolean valid = false;
        if (collection instanceof Collection)
        {
            valid = forAll((Collection) collection, predicate);
        }
        return valid;
    }

    /**
     * Returns <code>true</code> if the result of executing the <code>transformer</code> has a unique value for each
     * element in the source collection.
     * @param collection
     * @param transformer
     * @return unique
     */
    public static boolean isUnique(
            final Collection collection,
            final Transformer transformer)
    {
        boolean unique = true;
        final Set collected = new HashSet();
        for (final Iterator iterator = collection.iterator(); iterator.hasNext() && unique;)
        {
            final Object result = transformer.transform(iterator.next());
            if (collected.contains(result))
            {
                unique = false;
            }
            else
            {
                collected.add(result);
            }
        }
        return unique;
    }

    /**
     * Returns <code>true</code> if the result of executing the <code>transformer</code> has a unique value for each
     * element in the source collection.
     * @param collection
     * @param transformer
     * @return isUnique
     */
    public static boolean isUnique(
            final Object collection,
            final Transformer transformer)
    {
        boolean unique = collection != null;
        if (unique && collection != null && Collection.class.isAssignableFrom(collection.getClass()))
        {
            unique = isUnique((Collection) collection, transformer);
        }
        return unique;
    }

    /**
     * TODO: implement
     * @param collection
     * @return UnsupportedOperationException
     */
    public static Collection iterate(final Collection collection)
    {
        throw new UnsupportedOperationException(OCLCollections.class.getName() + ".iterate");
    }

    /**
     * Returns <code>true</true> when the argument expression evaluates true for one and only one element in the
     * collection. Returns <code>false</code> otherwise.
     * @param collection
     * @param predicate
     * @return found
     */
    public static boolean one(
            final Collection collection,
            final Predicate predicate)
    {
        boolean found = false;

        if (collection != null)
        {
            for (final Iterator iterator = collection.iterator(); iterator.hasNext();)
            {
                if (predicate.evaluate(iterator.next()))
                {
                    if (found)
                    {
                        found = false;
                        break;
                    }
                    found = true;
                }
            }
        }
        return found;
    }

    /**
     * <p/>
     * Returns <code>true</true> if <code>collection</code> is actually a Collection instance and if the
     * <code>predicate</code> expression evaluates true for one and only one element in the collection. Returns
     * <code>false</code> otherwise. </p>
     * @param collection
     * @param predicate
     * @return one((Collection) collection, predicate)
     */
    public static boolean one(
            final Object collection,
            final Predicate predicate)
    {
        return collection != null && Collection.class.isAssignableFrom(collection.getClass()) &&
                one((Collection) collection, predicate);
    }

    /**
     * Returns a subcollection of the source collection containing all elements for which the expression evaluates
     * <code>false</code>.
     * @param collection
     * @param predicate
     * @return CollectionUtils.selectRejected(collection, predicate)
     */
    public static Collection reject(
            final Collection collection,
            final Predicate predicate)
    {
        return CollectionUtils.selectRejected(collection, predicate);
    }

    /**
     * Returns a subcollection of the source collection containing all elements for which the expression evaluates
     * <code>true</code>.
     * @param collection
     * @param predicate
     * @return CollectionUtils.select(collection, predicate)
     */
    public static Collection select(
            final Collection collection,
            final Predicate predicate)
    {
        return CollectionUtils.select(collection, predicate);
    }

    /**
     * Returns a subcollection of the source collection containing all elements for which the expression evaluates
     * <code>true</code>.
     * @param collection
     * @param predicate
     * @return CollectionUtils.select((Collection) collection, predicate)
     */
    public static Collection select(
            final Object collection,
            final Predicate predicate)
    {
        return CollectionUtils.select((Collection) collection, predicate);
    }

    /**
     * TODO: implement
     * @param collection
     * @return UnsupportedOperationException
     */
    public static Collection sortedBy(final Collection collection)
    {
        throw new UnsupportedOperationException(OCLCollections.class.getName() + ".sortedBy");
    }

    /**
     * Converts the given object to a java.util.List implementation. If the object is not a collection type, then the
     * object is placed within a collection as the only element.
     *
     * @param object the object to convert.
     * @return the new List.
     */
    private static List objectToList(Object object)
    {
        List list = null;
        if (object instanceof Collection)
        {
            final Collection collection = (Collection) object;
            if (!(object instanceof List))
            {
                object = new ArrayList(collection);
            }
            list = (List) object;
        } else
        {
            list = new ArrayList();
            if (object != null)
            {
                list.add(object);
            }
        }
        return list;
    }
}