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.<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}