001package org.andromda.translation.ocl.validation;
002
003import java.util.ArrayList;
004import java.util.Arrays;
005import java.util.Collection;
006import java.util.Collections;
007import java.util.HashSet;
008import java.util.Iterator;
009import java.util.List;
010import java.util.Random;
011import java.util.Set;
012import java.util.TreeSet;
013import org.apache.commons.collections.Bag;
014import org.apache.commons.collections.CollectionUtils;
015import org.apache.commons.collections.Predicate;
016import org.apache.commons.collections.SetUtils;
017import org.apache.commons.collections.Transformer;
018import org.apache.commons.collections.bag.HashBag;
019import org.apache.commons.lang.StringUtils;
020
021/**
022 * Used to translated OCL collection expressions to their corresponding Java collection expressions.
023 */
024public final class OCLCollections
025{
026    /**
027     * Counts the number of occurrences of the argument item in the source collection.
028     * @param collection
029     * @param item
030     * @return CollectionUtils.cardinality(item, collection)
031     */
032    public static int count(
033            final Collection collection,
034            Object item)
035    {
036        return collection == null ? 0 : CollectionUtils.cardinality(item, collection);
037    }
038
039    /**
040     * Return true if the object is not an element of the collection, false otherwise.
041     * @param collection
042     * @param item
043     * @return !collection.contains(item
044     */
045    public static boolean excludes(
046            final Collection collection,
047            final Object item)
048    {
049        return collection == null || !collection.contains(item);
050    }
051
052    /**
053     * Returns true if all elements of the parameter collection are not present in the current collection, false
054     * otherwise.
055     * @param collection
056     * @param items
057     * @return excludesAll
058     */
059    public static boolean excludesAll(
060            final Collection collection,
061            final Collection items)
062    {
063        boolean excludesAll = true;
064        for (final Iterator iterator = items.iterator(); iterator.hasNext();)
065        {
066            final Object object = iterator.next();
067            if (!excludes(collection, object))
068            {
069                excludesAll = false;
070                break;
071            }
072        }
073        return excludesAll;
074    }
075
076    /**
077     * Returns true if the object is an element of the collection, false otherwise.
078     * @param collection
079     * @param item
080     * @return collection.contains(item)
081     */
082    public static boolean includes(
083            final Collection collection,
084            final Object item)
085    {
086        return collection != null && collection.contains(item);
087    }
088
089    /**
090     * Returns true if all elements of the parameter collection are present in the current collection, false otherwise.
091     * @param collection
092     * @param items
093     * @return collection.containsAll(items)
094     */
095    public static boolean includesAll(
096            final Collection collection,
097            final Collection items)
098    {
099        return collection != null && collection.containsAll(items);
100    }
101
102    /**
103     * Returns true if the collection contains no elements, false otherwise.
104     * @param collection
105     * @return collection.isEmpty()
106     */
107    public static boolean isEmpty(final Collection collection)
108    {
109        return (collection == null) || (collection.isEmpty());
110    }
111
112    /**
113     * Returns true if the argument is <code>null</code>, false otherwise.
114     * @param object
115     * @return isEmpty
116     */
117    public static boolean isEmpty(final Object object)
118    {
119        boolean isEmpty = object == null;
120        if (!isEmpty)
121        {
122            if (object instanceof Collection)
123            {
124                isEmpty = ((Collection) object).isEmpty();
125            }
126            else if (object instanceof String)
127            {
128                isEmpty = isEmpty((String) object);
129            }
130            else if (object != null && object.getClass().isArray())
131            {
132                isEmpty = ((Object[]) object).length == 0;
133            }
134        }
135        return isEmpty;
136    }
137
138    /**
139     * Returns true if the argument is either <code>null</code> or only contains whitespace characters, false
140     * otherwise.
141     * @param string
142     * @return isBlank(string)
143     */
144    public static boolean isEmpty(final String string)
145    {
146        return StringUtils.isBlank(string);
147    }
148
149    /**
150     * Returns true if the collection contains one or more elements, false otherwise.
151     * @param collection
152     * @return isEmpty(collection)
153     */
154    public static boolean notEmpty(final Collection collection)
155    {
156        return (collection != null) && !isEmpty(collection);
157    }
158
159    /**
160     * Returns true if the argument is not <code>null</code>, false otherwise.
161     * @param object
162     * @return notEmpty
163     */
164    public static boolean notEmpty(final Object object)
165    {
166        boolean notEmpty = object != null;
167        if (notEmpty)
168        {
169            if (object instanceof Collection)
170            {
171                notEmpty = !((Collection) object).isEmpty();
172            }
173            else if (object instanceof String)
174            {
175                notEmpty = notEmpty((String) object);
176            }
177            else if (object != null && object.getClass().isArray())
178            {
179                notEmpty = ((Object[]) object).length > 0;
180            }
181        }
182        return notEmpty;
183    }
184
185    /**
186     * Returns true if the argument is neither <code>null</code> nor only contains whitespace characters, false
187     * otherwise.
188     * @param string
189     * @return isNotBlank(string)
190     */
191    public static boolean notEmpty(final String string)
192    {
193        return StringUtils.isNotBlank(string);
194    }
195
196    /**
197     * Checks the instance of the <code>object</code> and makes sure its a Collection, if the object is a collection the
198     * size is checked and returned, if its NOT a collection, 0 is returned.
199     *
200     * @param object the object to check.
201     * @return the size of the collection
202     */
203    public static int size(final Object object)
204    {
205        int size = 0;
206        if (object != null)
207        {
208            if (object instanceof Collection)
209            {
210                size = size((Collection) object);
211            } else if (object.getClass().isArray())
212            {
213                size = ((Object[]) object).length;
214            }
215        }
216        return size;
217    }
218
219    /**
220     * Returns the number of elements in the collection.
221     * @param collection
222     * @return collection.size()
223     */
224    public static int size(final Collection collection)
225    {
226        int size = 0;
227        if (collection != null)
228        {
229            size = collection.size();
230        }
231        return size;
232    }
233
234    /**
235     * Returns the sum of all the elements in the collection. Every element must extend Number or this method
236     * will throw an exception.
237     *
238     * @param collection a collection containing only classes extending Number
239     * @return the sum of all the elements in the collection
240     */
241    public static double sum(final Object collection)
242    {
243        double sum = 0;
244        if (collection != null)
245        {
246            if (collection instanceof Collection)
247            {
248                // TODO Fix infinite loop
249                sum = sum(collection);
250            }
251            else if (collection.getClass().isArray())
252            {
253                sum = sum(Arrays.asList((Object[]) collection));
254            }
255        }
256        return sum;
257    }
258
259    /**
260     * Returns the sum of all the element in the collection. Every element must extend Number or this method
261     * will throw an exception.
262     *
263     * @param collection a collection containing only classes extending Number
264     * @return the sum of all the elements in the collection
265     */
266    public static double sum(final Collection collection)
267    {
268        double sum = 0;
269        if (collection != null && !collection.isEmpty())
270        {
271            for (final Iterator iterator = collection.iterator(); iterator.hasNext();)
272            {
273                Object object = iterator.next();
274                if (object instanceof Number)
275                {
276                    sum += ((Number) object).doubleValue();
277                } else
278                {
279                    throw new UnsupportedOperationException(
280                            "In order to calculate the sum of a collection\'s elements " +
281                                    "all of them must extend Number, found: " + object.getClass().getName());
282                }
283            }
284        }
285        return sum;
286    }
287
288    /**
289     * Appends the item to the list.
290     * @param list
291     * @param item
292     * @return true if the operation was a success
293     */
294    public static boolean append(
295            final List list,
296            final Object item)
297    {
298        return list == null ? false : list.add(item);
299    }
300
301    /**
302     * Insert the item into the first position of the list.
303     * @param list
304     * @param item
305     * @return the element previously at the first position
306     */
307    public static Object prepend(
308            final List list,
309            final Object item)
310    {
311        return list.set(0, item);
312    }
313
314    /**
315     * Appends the item to the bag.
316     * @param collection
317     * @param item
318     * @return true if the operation was a success
319     */
320    public static boolean append(
321            final Bag collection,
322            final Object item)
323    {
324        return collection == null ? false : collection.add(item);
325    }
326
327    /**
328     * Returns the argument as a bag.
329     * @param collection
330     * @return new HashBag(collection)
331     */
332    public static Bag asBag(final Collection collection)
333    {
334        return collection == null ? new HashBag() : new HashBag(collection);
335    }
336
337    /**
338     * Returns the argument as an ordered set.
339     * @param collection
340     * @return SetUtils.orderedSet(new TreeSet(collection))
341     */
342    public static Set asOrderedSet(final Collection collection)
343    {
344        return collection == null ? Collections.emptySet() : SetUtils.orderedSet(new TreeSet(collection));
345    }
346
347    /**
348     * Returns the argument as a list.
349     * @param collection
350     * @return new ArrayList(collection)
351     */
352    public static List asSequence(final Collection collection)
353    {
354        return collection == null ? Collections.emptyList() : new ArrayList(collection);
355    }
356
357    /**
358     * Returns the argument as a set.
359     * @param collection
360     * @return new HashSet(collection)
361     */
362    public static Set asSet(final Collection collection)
363    {
364        return collection == null ? Collections.emptySet() : new HashSet(collection);
365    }
366
367    /**
368     * Returns the element at the specified index in the argument list.
369     * @param list
370     * @param index
371     * @return list.get(index)
372     */
373    public static Object at(
374            final List list,
375            final int index)
376    {
377        return list == null ? null : list.get(index);
378    }
379
380    /**
381     * Removes all occurrences of the item in the source collection.
382     * @param collection
383     * @param item
384     * @return true if one or more elements were removed
385     */
386    public static boolean excluding(
387            final Collection collection,
388            final Object item)
389    {
390        return collection == null ? false : collection.remove(item);
391    }
392
393    /**
394     * Adds the item to the list
395     * @param collection
396     * @param item
397     * @return true if the element was added
398     */
399    public static boolean including(
400            final Collection collection,
401            final Object item)
402    {
403        return collection == null ? false : collection.add(item);
404    }
405
406    /**
407     * Recursively flattens this collection, this method returns a Collection containing no nested Collection
408     * instances.
409     * @param collection
410     * @return flattenedCollection.addAll(flatten((Collection) object))
411     */
412    public static Collection flatten(final Collection collection)
413    {
414        final Collection flattenedCollection = new ArrayList();
415        for (final Iterator iterator = collection.iterator(); iterator.hasNext();)
416        {
417            final Object object = iterator.next();
418            if (object instanceof Collection)
419            {
420                flattenedCollection.addAll(flatten((Collection) object));
421            }
422            else
423            {
424                flattenedCollection.add(object);
425            }
426        }
427
428        return flattenedCollection;
429    }
430
431    /**
432     * Returns the index in this list of the first occurrence of the specified element, or -1 if this list does not
433     * contain this element. More formally, returns the lowest index i such that (o == null ? get(i) = =null :
434     * o.equals(get(i))), or -1 if there is no such index.
435     * @param collection
436     * @param item
437     * @return collection.indexOf(item)
438     */
439    public static int indexOf(
440            final List collection,
441            final Object item)
442    {
443        return collection == null ? -1 : collection.indexOf(item);
444    }
445
446    /**
447     * Insert the item at the specified index into the collection.
448     * @param collection
449     * @param index
450     * @param item
451     */
452    public static void insertAt(
453            final List collection,
454            int index,
455            Object item)
456    {
457        collection.add(index, item);
458    }
459
460    /**
461     * Returns the collection of elements common in both argument collections.
462     * @param first
463     * @param second
464     * @return CollectionUtils.intersection(first, second)
465     */
466    public static Collection intersection(
467            final Collection first,
468            final Collection second)
469    {
470        return CollectionUtils.intersection(first, second);
471    }
472
473    /**
474     * Returns the union of both collections into a single collection.
475     * @param first
476     * @param second
477     * @return CollectionUtils.union(first, second)
478     */
479    public static Collection union(
480            final Collection first,
481            final Collection second)
482    {
483        return CollectionUtils.union(first, second);
484    }
485
486    /**
487     * Returns the last element in the collection.
488     *
489     * @param object the collection or single instance which will be converted to a collection.
490     * @return the last object of the collection or the object itself if the object is not a collection instance (or
491     *         null if the object is null or an empty collection).
492     */
493    public static Object last(final Object object)
494    {
495        Object last = null;
496        final List list = objectToList(object);
497        if (!list.isEmpty())
498        {
499            last = list.get(list.size() - 1);
500        }
501        return last;
502    }
503
504    /**
505     * Returns the first element in the collection.
506     *
507     * @param object the collection or single instance which will be converted to a collection.
508     * @return the first object of the collection or the object itself if the object is not a collection instance (or
509     *         null if the object is null or an empty collection).
510     */
511    public static Object first(final Object object)
512    {
513        Object first = null;
514        final List list = objectToList(object);
515        if (!list.isEmpty())
516        {
517            first = list.get(0);
518        }
519        return first;
520    }
521
522    /**
523     * Returns those element that are contained in only one of both collections.
524     * @param first
525     * @param second
526     * @return CollectionUtils.disjunction(first, second)
527     */
528    public static Collection symmetricDifference(
529            final Collection first,
530            final Collection second)
531    {
532        return CollectionUtils.disjunction(first, second);
533    }
534
535    /**
536     * TODO: implement
537     * @param collection
538     * @return UnsupportedOperationException
539     */
540    public static Set subOrderedSet(final Set collection)
541    {
542        throw new UnsupportedOperationException(OCLCollections.class.getName() + ".subOrderedSet");
543    }
544
545    /**
546     * TODO: implement
547     * @param collection
548     * @return UnsupportedOperationException
549     */
550    public static List subSequence(final List collection)
551    {
552        throw new UnsupportedOperationException(OCLCollections.class.getName() + ".subSequence");
553    }
554
555    /**
556     * Returns a random element from the collection for which the argument expression evaluates true.
557     * @param collection
558     * @param predicate
559     * @return selectedElements.get(random.nextInt(selectedElements.size())
560     */
561    public static Object any(
562            final Collection collection,
563            final Predicate predicate)
564    {
565        final List selectedElements = new ArrayList(select(collection, predicate));
566        final Random random = new Random(System.currentTimeMillis());
567        return selectedElements.isEmpty() ? null : selectedElements.get(random.nextInt(selectedElements.size()));
568    }
569
570    /**
571     * Returns the collection of Objects that results from executing the transformer on each individual element in the
572     * source collection.
573     * @param collection
574     * @param transformer
575     * @return CollectionUtils.collect(collection, transformer)
576     */
577    public static Collection collect(
578            final Collection collection,
579            final Transformer transformer)
580    {
581        return CollectionUtils.collect(collection, transformer);
582    }
583
584    /**
585     * TODO: implement
586     * @param collection
587     * @return UnsupportedOperationException
588     */
589    public static Collection collectNested(final Collection collection)
590    {
591        throw new UnsupportedOperationException(OCLCollections.class.getName() + ".collectNested");
592    }
593
594    /**
595     * Returns true if a predicate is true for at least one element of a collection. <p/>A null collection or predicate
596     * returns false.
597     * @param collection
598     * @param predicate
599     * @return CollectionUtils.exists(collection, predicate)
600     */
601    public static boolean exists(
602            final Collection collection,
603            final Predicate predicate)
604    {
605        return CollectionUtils.exists(collection, predicate);
606    }
607
608    /**
609     * Returns true if a predicate is true for at least one element of a collection. <p/>A null collection or predicate
610     * returns false.
611     * @param collection
612     * @param predicate
613     * @return exists((Collection) collection, predicate)
614     */
615    public static boolean exists(
616            final Object collection,
617            final Predicate predicate)
618    {
619        return collection instanceof Collection ? exists((Collection) collection, predicate) : false;
620    }
621
622    /**
623     * <p/>
624     * Executes every <code>predicate</code> for the given collectoin, if one evaluates to <code>false</code> this
625     * operation returns <code>false</code>, otherwise <code>true</code> is returned. </p> If the input collection or
626     * closure is null <code>false</code> is returned.
627     * @param collection
628     * @param predicate
629     * @return true if every evaluated predicate returns true, false otherwise.
630     */
631    public static boolean forAll(
632            final Collection collection,
633            final Predicate predicate)
634    {
635        boolean valid = collection != null;
636        if (valid && collection != null)
637        {
638            for (final Iterator iterator = collection.iterator(); iterator.hasNext();)
639            {
640                final Object object = iterator.next();
641                valid = predicate.evaluate(object);
642                if (!valid)
643                {
644                    break;
645                }
646            }
647        }
648        return valid;
649    }
650
651    /**
652     * <p/>
653     * Executes every <code>predicate</code> for the given collection, if one evaluates to <code>false</code> this
654     * operation returns <code>false</code>, otherwise <code>true</code> is returned. </p> If the input collection or
655     * closure is null <code>false</code> is returned.
656     * @param collection
657     * @param predicate
658     *
659     * @return true if every evaluated predicate returns true, false otherwise.
660     */
661    public static boolean forAll(
662            final Object collection,
663            final Predicate predicate)
664    {
665        boolean valid = false;
666        if (collection instanceof Collection)
667        {
668            valid = forAll((Collection) collection, predicate);
669        }
670        return valid;
671    }
672
673    /**
674     * Returns <code>true</code> if the result of executing the <code>transformer</code> has a unique value for each
675     * element in the source collection.
676     * @param collection
677     * @param transformer
678     * @return unique
679     */
680    public static boolean isUnique(
681            final Collection collection,
682            final Transformer transformer)
683    {
684        boolean unique = true;
685        final Set collected = new HashSet();
686        for (final Iterator iterator = collection.iterator(); iterator.hasNext() && unique;)
687        {
688            final Object result = transformer.transform(iterator.next());
689            if (collected.contains(result))
690            {
691                unique = false;
692            }
693            else
694            {
695                collected.add(result);
696            }
697        }
698        return unique;
699    }
700
701    /**
702     * Returns <code>true</code> if the result of executing the <code>transformer</code> has a unique value for each
703     * element in the source collection.
704     * @param collection
705     * @param transformer
706     * @return isUnique
707     */
708    public static boolean isUnique(
709            final Object collection,
710            final Transformer transformer)
711    {
712        boolean unique = collection != null;
713        if (unique && collection != null && Collection.class.isAssignableFrom(collection.getClass()))
714        {
715            unique = isUnique((Collection) collection, transformer);
716        }
717        return unique;
718    }
719
720    /**
721     * TODO: implement
722     * @param collection
723     * @return UnsupportedOperationException
724     */
725    public static Collection iterate(final Collection collection)
726    {
727        throw new UnsupportedOperationException(OCLCollections.class.getName() + ".iterate");
728    }
729
730    /**
731     * Returns <code>true</true> when the argument expression evaluates true for one and only one element in the
732     * collection. Returns <code>false</code> otherwise.
733     * @param collection
734     * @param predicate
735     * @return found
736     */
737    public static boolean one(
738            final Collection collection,
739            final Predicate predicate)
740    {
741        boolean found = false;
742
743        if (collection != null)
744        {
745            for (final Iterator iterator = collection.iterator(); iterator.hasNext();)
746            {
747                if (predicate.evaluate(iterator.next()))
748                {
749                    if (found)
750                    {
751                        found = false;
752                        break;
753                    }
754                    found = true;
755                }
756            }
757        }
758        return found;
759    }
760
761    /**
762     * <p/>
763     * Returns <code>true</true> if <code>collection</code> is actually a Collection instance and if the
764     * <code>predicate</code> expression evaluates true for one and only one element in the collection. Returns
765     * <code>false</code> otherwise. </p>
766     * @param collection
767     * @param predicate
768     * @return one((Collection) collection, predicate)
769     */
770    public static boolean one(
771            final Object collection,
772            final Predicate predicate)
773    {
774        return collection != null && Collection.class.isAssignableFrom(collection.getClass()) &&
775                one((Collection) collection, predicate);
776    }
777
778    /**
779     * Returns a subcollection of the source collection containing all elements for which the expression evaluates
780     * <code>false</code>.
781     * @param collection
782     * @param predicate
783     * @return CollectionUtils.selectRejected(collection, predicate)
784     */
785    public static Collection reject(
786            final Collection collection,
787            final Predicate predicate)
788    {
789        return CollectionUtils.selectRejected(collection, predicate);
790    }
791
792    /**
793     * Returns a subcollection of the source collection containing all elements for which the expression evaluates
794     * <code>true</code>.
795     * @param collection
796     * @param predicate
797     * @return CollectionUtils.select(collection, predicate)
798     */
799    public static Collection select(
800            final Collection collection,
801            final Predicate predicate)
802    {
803        return CollectionUtils.select(collection, predicate);
804    }
805
806    /**
807     * Returns a subcollection of the source collection containing all elements for which the expression evaluates
808     * <code>true</code>.
809     * @param collection
810     * @param predicate
811     * @return CollectionUtils.select((Collection) collection, predicate)
812     */
813    public static Collection select(
814            final Object collection,
815            final Predicate predicate)
816    {
817        return CollectionUtils.select((Collection) collection, predicate);
818    }
819
820    /**
821     * TODO: implement
822     * @param collection
823     * @return UnsupportedOperationException
824     */
825    public static Collection sortedBy(final Collection collection)
826    {
827        throw new UnsupportedOperationException(OCLCollections.class.getName() + ".sortedBy");
828    }
829
830    /**
831     * Converts the given object to a java.util.List implementation. If the object is not a collection type, then the
832     * object is placed within a collection as the only element.
833     *
834     * @param object the object to convert.
835     * @return the new List.
836     */
837    private static List objectToList(Object object)
838    {
839        List list = null;
840        if (object instanceof Collection)
841        {
842            final Collection collection = (Collection) object;
843            if (!(object instanceof List))
844            {
845                object = new ArrayList(collection);
846            }
847            list = (List) object;
848        } else
849        {
850            list = new ArrayList();
851            if (object != null)
852            {
853                list.add(object);
854            }
855        }
856        return list;
857    }
858}