001/**
002 * Copyright 2010-2014 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.common.util;
017
018import static com.google.common.collect.Lists.newArrayList;
019import static org.apache.commons.lang3.StringUtils.isBlank;
020
021import java.io.File;
022import java.io.IOException;
023import java.io.StringReader;
024import java.util.ArrayList;
025import java.util.Arrays;
026import java.util.Collection;
027import java.util.Collections;
028import java.util.HashMap;
029import java.util.Iterator;
030import java.util.LinkedHashSet;
031import java.util.List;
032import java.util.Map;
033import java.util.Set;
034import java.util.TreeSet;
035
036import org.apache.commons.io.IOUtils;
037import org.apache.commons.lang3.StringUtils;
038import org.kuali.common.util.nullify.NullUtils;
039
040import com.google.common.base.Predicate;
041import com.google.common.collect.ImmutableList;
042import com.google.common.collect.Iterables;
043
044public class CollectionUtils {
045
046        private enum BlanksFilter implements Predicate<String> {
047                INSTANCE;
048                @Override
049                public boolean apply(String input) {
050                        return isBlank(input);
051                }
052        }
053
054        public static boolean hasBlanks(Collection<String> collection) {
055                return !getBlanks(collection).isEmpty();
056        }
057
058        public static Collection<String> getBlanks(Collection<String> collection) {
059                return newArrayList(Iterables.filter(collection, BlanksFilter.INSTANCE));
060        }
061
062        /**
063         * Returns a new unmodifiable list containing the elements from <code>list</code>
064         * 
065         * @deprecated See ListUtils.newArrayList() instead
066         */
067        @Deprecated
068        public static <T> List<T> unmodifiableCopy(List<T> list) {
069                return Collections.unmodifiableList(new ArrayList<T>(list));
070        }
071
072        /**
073         * Get an unmodifiable list from the single element. Return emptyList() if element is null.
074         * 
075         * @deprecated Use CollectionUtils.singletonList() instead
076         */
077        @Deprecated
078        public static <T> List<T> unmodifiableList(T element) {
079                List<T> list = toEmptyList(element);
080                return Collections.unmodifiableList(list);
081        }
082
083        /**
084         * Get an unmodifiable list from elements
085         * 
086         * @deprecated Use ImmutableList.copyOf(elements) instead
087         */
088        @Deprecated
089        @SafeVarargs
090        public static <T> List<T> unmodifiableList(T... elements) {
091                return Collections.unmodifiableList(Arrays.asList(elements));
092        }
093
094        /**
095         * If the CSV is whitespace, the empty string, <code>null</code>, <code>"null"</code>, or <code>"none"</code>, return an empty list.
096         */
097        public static List<String> getNoneSensitiveListFromCSV(String csv) {
098                if (StringUtils.isBlank(csv) || NullUtils.isNullOrNone(csv)) {
099                        return Collections.<String> emptyList();
100                } else {
101                        return CollectionUtils.getTrimmedListFromCSV(csv);
102                }
103        }
104
105        /**
106         * Remove any Strings from the list that do not match the filter and then sort the ones that remain
107         * 
108         * @return The list of strings that were filtered out.
109         * @deprecated
110         */
111        @Deprecated
112        public static List<String> filterAndSort(List<String> strings, StringFilter filter) {
113                List<String> excluded = filter(strings, filter);
114                Collections.sort(strings);
115                return excluded;
116        }
117
118        /**
119         * Remove any Strings from the list that do not match the filter and then sort the ones that remain
120         * 
121         * @return The list of strings that were filtered out.
122         */
123        public static List<String> filterAndSortStrings(List<String> strings, org.kuali.common.util.filter.StringFilter filter) {
124                List<String> excluded = filterStrings(strings, filter);
125                Collections.sort(strings);
126                return excluded;
127        }
128
129        /**
130         * Remove any Strings from the collection that do not match the filter
131         */
132        public static List<String> filterStrings(Collection<String> strings, org.kuali.common.util.filter.StringFilter filter) {
133                List<String> excluded = new ArrayList<String>();
134                Iterator<String> itr = strings.iterator();
135                while (itr.hasNext()) {
136                        String string = itr.next();
137                        if (!filter.include(string)) {
138                                excluded.add(string);
139                                itr.remove();
140                        }
141                }
142                return excluded;
143        }
144
145        /**
146         * Remove any Strings from the collection that do not match the filter
147         * 
148         * @deprecated
149         */
150        @Deprecated
151        public static List<String> filter(Collection<String> strings, StringFilter filter) {
152                List<String> excluded = new ArrayList<String>();
153                Iterator<String> itr = strings.iterator();
154                while (itr.hasNext()) {
155                        String string = itr.next();
156                        if (!filter.include(string)) {
157                                excluded.add(string);
158                                itr.remove();
159                        }
160                }
161                return excluded;
162        }
163
164        /**
165         * Null safe method for converting an array of objects into a list. Never returns null.
166         */
167        public static List<Object> asList(Object... objects) {
168                List<Object> list = new ArrayList<Object>();
169                if (objects == null) {
170                        return list;
171                }
172                for (Object element : objects) {
173                        if (element != null) {
174                                list.add(element);
175                        }
176                }
177                return list;
178        }
179
180        /**
181         * Null safe method for converting an untyped array of classes into a list. Never returns null.
182         */
183        public static List<Class<?>> asList(Class<?>... classes) {
184                List<Class<?>> list = new ArrayList<Class<?>>();
185                if (classes == null) {
186                        return list;
187                }
188                for (Class<?> element : classes) {
189                        if (element != null) {
190                                list.add(element);
191                        }
192                }
193                return list;
194        }
195
196        /**
197         * Return an array of int's that represents as even of a split as possible
198         * 
199         * For example: passing in 100,7 returns 15, 15, 14, 14, 14, 14, 14
200         * 
201         * @param numerator
202         * @param denominator
203         * @return
204         */
205        public static int[] getDivideEvenly(int number, int howManyWays) {
206                Assert.isTrue(howManyWays > 0, "howManyWays must be a positive integer");
207                int quotient = number / howManyWays;
208                int remainder = number % howManyWays;
209
210                int[] lengths = new int[howManyWays];
211                for (int i = 0; i < howManyWays; i++) {
212                        int length = i < remainder ? quotient + 1 : quotient;
213                        lengths[i] = length;
214                }
215                return lengths;
216        }
217
218        /**
219         * Split <code>elements</code> evenly into separate lists divided up <code>howManyWays</code>
220         */
221        public static final <T> List<List<T>> splitEvenly(List<T> elements, int howManyWays) {
222                // Can't split 2 things 3 ways
223                if (howManyWays > elements.size()) {
224                        howManyWays = elements.size();
225                }
226                int[] lengths = getDivideEvenly(elements.size(), howManyWays);
227                int offset = 0;
228                List<List<T>> listOfLists = new ArrayList<List<T>>();
229                for (int i = 0; i < lengths.length; i++) {
230                        int length = lengths[i];
231                        List<T> sublist = new ArrayList<T>();
232                        for (int j = offset; j < offset + length; j++) {
233                                sublist.add(elements.get(j));
234                        }
235                        listOfLists.add(sublist);
236                        offset += length;
237                }
238                return listOfLists;
239        }
240
241        /**
242         * Prefix the strings passed in with their position in the list (left padded with zero's). The padding is the number of digits in the size of the list. A list with 100 elements
243         * will return strings prefixed with 000, 001, etc.
244         */
245        public static final List<String> getSequencedStrings(List<String> strings, int initialSequenceNumber) {
246                List<String> sequencedStrings = new ArrayList<String>();
247                int size = strings.size();
248                int length = new Integer(size).toString().length();
249                String prefix = StringUtils.repeat("0", length);
250                for (String string : strings) {
251                        String sequence = StringUtils.right(prefix + (initialSequenceNumber++), length);
252                        String sequencedString = sequence + "-" + string;
253                        sequencedStrings.add(sequencedString);
254                }
255                return sequencedStrings;
256        }
257
258        /**
259         * Prefix the strings passed in with their position in the list (left padded with zero's). The padding is the number of digits in the size of the list. A list with 100 elements
260         * will return strings prefixed with 000, 001, etc.
261         */
262        public static final List<String> getSequencedStrings(List<String> strings) {
263                return getSequencedStrings(strings, 0);
264        }
265
266        /**
267         * Return a new <code>List</code> containing the unique set of strings from <code>strings</code>
268         */
269        public static final List<String> getUniqueStrings(List<String> strings) {
270                LinkedHashSet<String> unique = new LinkedHashSet<String>(strings);
271                return new ArrayList<String>(unique);
272        }
273
274        public static final List<File> getUniqueFiles(List<File> files) {
275                LinkedHashSet<File> unique = new LinkedHashSet<File>(files);
276                return new ArrayList<File>(unique);
277        }
278
279        public static final List<String> getLines(String s) {
280                if (s == null) {
281                        return Collections.<String> emptyList();
282                }
283                try {
284                        return IOUtils.readLines(new StringReader(s));
285                } catch (IOException e) {
286                        throw new IllegalStateException(e);
287                }
288        }
289
290        /**
291         * Return a new list containing the unique set of strings contained in both lists
292         */
293        public static final List<String> combineStringsUniquely(List<String> list1, List<String> list2) {
294                List<String> newList = getUniqueStrings(list1);
295                for (String element : list2) {
296                        if (!newList.contains(element)) {
297                                newList.add(element);
298                        }
299                }
300                return newList;
301        }
302
303        protected static final <T> T getNewInstance(Class<T> c) {
304                try {
305                        return c.newInstance();
306                } catch (IllegalAccessException e) {
307                        throw new IllegalArgumentException(e);
308                } catch (InstantiationException e) {
309                        throw new IllegalArgumentException(e);
310                }
311        }
312
313        /**
314         * Create a new list containing new instances of <code>c</code>
315         */
316        public static final <T> List<T> getNewList(Class<T> c, int size) {
317                List<T> list = new ArrayList<T>();
318                for (int i = 0; i < size; i++) {
319                        T element = getNewInstance(c);
320                        list.add(element);
321                }
322                return list;
323        }
324
325        /**
326         * Return a list containing only the elements where the corresponding index in the <code>includes</code> list is <code>true</code>. <code>includes</code> and <code>list</code>
327         * must be the same size.
328         */
329        public static final <T> List<T> getList(List<Boolean> includes, List<T> list) {
330                Assert.isTrue(includes.size() == list.size());
331                List<T> included = new ArrayList<T>();
332                for (int i = 0; i < includes.size(); i++) {
333                        if (includes.get(i)) {
334                                included.add(list.get(i));
335                        }
336                }
337                return included;
338        }
339
340        /**
341         * Combine the list of lists into a single list
342         */
343        public static final <T> List<T> combineLists(List<List<T>> listOfLists) {
344                List<T> combined = new ArrayList<T>();
345                for (List<T> list : listOfLists) {
346                        combined.addAll(list);
347                }
348                return combined;
349        }
350
351        /**
352         * Combine the list of maps into a single map
353         */
354        public static final <K, V> Map<K, V> combineMaps(List<Map<K, V>> listOfMaps) {
355                Map<K, V> combined = new HashMap<K, V>();
356                for (Map<K, V> map : listOfMaps) {
357                        combined.putAll(map);
358                }
359                return combined;
360        }
361
362        /**
363         * Return a combined list where <code>required</code> is always the first element in the list
364         */
365        public static final <T> List<T> combine(T element, List<T> list) {
366                Assert.notNull(element, "element is required");
367                if (list == null) {
368                        return Collections.singletonList(element);
369                } else {
370                        List<T> combined = new ArrayList<T>();
371                        // Always insert required as the first element in the list
372                        combined.add(element);
373                        // Add the other elements
374                        for (T optional : list) {
375                                combined.add(optional);
376                        }
377                        return combined;
378                }
379        }
380
381        /**
382         * If <code>map==null</code> return emptyMap(), otherwise return <code>map</code>
383         */
384        public static final <K, V> Map<K, V> toEmptyMap(Map<K, V> map) {
385                if (map == null) {
386                        return Collections.emptyMap();
387                } else {
388                        return map;
389                }
390        }
391
392        /**
393         * If <code>map==null</code> return <code>new HashMap<K,V>()</code>, otherwise return <code>map</code>
394         */
395        public static final <K, V> Map<K, V> toModifiableEmptyMap(Map<K, V> map) {
396                if (map == null) {
397                        return new HashMap<K, V>();
398                } else {
399                        return map;
400                }
401        }
402
403        /**
404         * If <code>key==null</code> OR <code>value==null</code> return <code>new HashMap<K,V>()</code> otherwise return
405         * <code>new HashMap<K, V>(Collections.singletonMap(key, value))</code>
406         */
407        public static final <K, V> Map<K, V> toModifiableEmptyMap(K key, V value) {
408                if (key == null || value == null) {
409                        return new HashMap<K, V>();
410                } else {
411                        return new HashMap<K, V>(Collections.singletonMap(key, value));
412                }
413        }
414
415        /**
416         * If <code>key==null</code> OR <code>value==null</code> return an empty map otherwise return a singleton map.
417         */
418        public static final <K, V> Map<K, V> toEmptyMap(K key, V value) {
419                if (key == null || value == null) {
420                        return Collections.emptyMap();
421                } else {
422                        return Collections.singletonMap(key, value);
423                }
424        }
425
426        /**
427         * If <code>o==null</code> return <code>Collections.&lt;T> emptyList()</code> otherwise return <code>Collections.singletonList(o)</code>
428         */
429        public static final <T> List<T> toEmptyList(T o) {
430                if (o == null) {
431                        return Collections.<T> emptyList();
432                } else {
433                        return Collections.singletonList(o);
434                }
435        }
436
437        /**
438         * Returns an immutable list containing only the specified object. The returned list is serializable.
439         * 
440         * @throws IllegalArgumentException
441         *             if object is null
442         */
443        public static final <T> List<T> singletonList(T o) {
444                if (o != null) {
445                        return Collections.singletonList(o);
446                } else {
447                        throw new IllegalArgumentException("nulls not allowed");
448                }
449        }
450
451        /**
452         * Add keys and values to map. Keys and values must be the same size (or both null). Map cannot be null.
453         */
454        public static final <K, V> void combine(Map<K, V> map, List<K> keys, List<V> values) {
455                keys = toEmptyList(keys);
456                values = toEmptyList(values);
457                Assert.isTrue(keys.size() == values.size(), "sizes must match");
458                Assert.notNull(map, "map is null");
459                for (int i = 0; i < keys.size(); i++) {
460                        K key = keys.get(i);
461                        V value = values.get(i);
462                        map.put(key, value);
463                }
464        }
465
466        /**
467         * If <code>list==null</code> return an empty list otherwise return <code>list</code>
468         */
469        public static final <T> List<T> toEmptyList(List<T> list) {
470                if (list == null) {
471                        return Collections.emptyList();
472                } else {
473                        return list;
474                }
475        }
476
477        public static final <T> List<T> toNullIfEmpty(List<T> list) {
478                if (isEmpty(list)) {
479                        return null;
480                } else {
481                        return list;
482                }
483        }
484
485        public static final <T> Collection<T> toNullIfEmpty(Collection<T> c) {
486                if (isEmpty(c)) {
487                        return null;
488                } else {
489                        return c;
490                }
491        }
492
493        public static final <T> List<T> getPreFilledList(int size, T value) {
494                if (value == null || size < 1) {
495                        return Collections.<T> emptyList();
496                } else {
497                        List<T> list = new ArrayList<T>(size);
498                        for (int i = 0; i < size; i++) {
499                                list.add(value);
500                        }
501                        return list;
502                }
503        }
504
505        public static final String getSpaceSeparatedCSV(List<String> strings) {
506                return getStringWithSeparator(strings, ", ");
507        }
508
509        public static final String getStringWithSeparator(List<?> list, String separator) {
510                list = toEmptyList(list);
511                StringBuilder sb = new StringBuilder();
512                for (int i = 0; i < toEmptyList(list).size(); i++) {
513                        if (i != 0) {
514                                sb.append(separator);
515                        }
516                        Object element = list.get(i);
517                        if (element != null) {
518                                sb.append(element.toString());
519                        } else {
520                                sb.append(NullUtils.NULL);
521                        }
522                }
523                return sb.toString();
524        }
525
526        public static final String toCSV(List<Integer> integers) {
527                Assert.noNulls(integers);
528                StringBuilder sb = new StringBuilder();
529                for (int i = 0; i < integers.size(); i++) {
530                        if (i != 0) {
531                                sb.append(",");
532                        }
533                        sb.append(integers.get(i));
534                }
535                return sb.toString();
536        }
537
538        public static final String asCSV(List<String> strings) {
539                return getCSV(strings);
540        }
541
542        public static final String getCSV(List<String> strings) {
543                return getStringWithSeparator(strings, ",");
544        }
545
546        public static final String getSpaceSeparatedString(List<?> list) {
547                return getStringWithSeparator(list, " ");
548        }
549
550        public static final Object[] toObjectArray(List<Object> objects) {
551                return objects.toArray(new Object[objects.size()]);
552        }
553
554        public static final String[] toStringArray(List<String> strings) {
555                return strings.toArray(new String[strings.size()]);
556        }
557
558        public static final boolean isEmpty(Collection<?> c) {
559                return c == null || c.size() == 0;
560        }
561
562        public static final boolean isEmpty(Map<?, ?> m) {
563                return m == null || m.size() == 0;
564        }
565
566        public static final List<String> sortedMerge(List<String> list, String csv) {
567                Set<String> set = new TreeSet<String>();
568                set.addAll(toEmptyList(list));
569                set.addAll(getTrimmedListFromCSV(csv));
570                return new ArrayList<String>(set);
571        }
572
573        public static final List<String> getTrimmedListFromCSV(String csv) {
574                if (StringUtils.isBlank(csv)) {
575                        return Collections.<String> emptyList();
576                }
577                String[] tokens = Str.splitAndTrimCSV(csv);
578                List<String> list = new ArrayList<String>();
579                list.addAll(Arrays.asList(tokens));
580                return list;
581        }
582
583        public static final <T> List<T> nullSafeCombine(List<T> list1, List<T> list2) {
584                List<T> combined = new ArrayList<T>();
585                if (!isEmpty(list1)) {
586                        combined.addAll(list1);
587                }
588                if (!isEmpty(list2)) {
589                        combined.addAll(list2);
590                }
591                return combined;
592        }
593
594        public static final List<String> combineStrings(List<String> list1, List<String> list2, List<String> list3) {
595                List<String> combined = new ArrayList<String>();
596                nullSafeAdd(combined, list1);
597                nullSafeAdd(combined, list2);
598                nullSafeAdd(combined, list3);
599                return combined;
600        }
601
602        /**
603         * Return a new list containing all of the strings from both lists with string added in between the strings from both lists
604         */
605        public static final List<String> combineStrings(List<String> list1, String string, List<String> list2) {
606                return combineStrings(list1, toEmptyList(string), list2);
607        }
608
609        /**
610         * Return a new list containing all of the strings from both lists
611         */
612        public static final List<String> combineStrings(List<String> list1, List<String> list2) {
613                return combineStrings(list1, (String) null, list2);
614        }
615
616        /**
617         * Return a new list containing all of the strings from both lists
618         */
619        @SafeVarargs
620        public static final List<String> combineStrings(List<String>... lists) {
621                List<String> combined = new ArrayList<String>();
622                for (List<String> list : lists) {
623                        combined.addAll(ImmutableList.copyOf(list));
624                }
625                return combined;
626        }
627
628        /**
629         * Return a new list containing all of the elements from the lists passed in
630         */
631        public static final <T> List<T> combine(List<T> list1, List<T> list2) {
632                return combine(list1, list2, null);
633        }
634
635        public static final <T> List<T> combine(List<T> list1, List<T> list2, List<T> list3) {
636                List<T> combined = new ArrayList<T>();
637                combined.addAll(toEmptyList(list1));
638                combined.addAll(toEmptyList(list2));
639                combined.addAll(toEmptyList(list3));
640                return combined;
641        }
642
643        public static final <T> void nullSafeAdd(List<T> list1, List<T> list2) {
644                if (list2 != null) {
645                        list1.addAll(list2);
646                }
647        }
648
649        /**
650         * Return <code>true</code> if <code>s</code> contains any of the strings from <code>strings</code>
651         */
652        public static final boolean containsAny(String s, List<String> strings) {
653                for (String string : strings) {
654                        if (StringUtils.contains(s, string)) {
655                                return true;
656                        }
657                }
658                return false;
659        }
660}