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