package org.rr.commons.utils; import static org.rr.commons.utils.StringUtil.EMPTY; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.rr.commons.collection.CompoundList; import org.rr.commons.collection.InternalStringList; public final class ListUtils implements Serializable { private static final long serialVersionUID = 5442821752302966293L; private ListUtils() { } /** * Creates a <code>String</code> that consists of a number of substrings * in an array. * * @param values * A n'th-dimensional array that contains the substrings to be * joined. * @param delimiter * The character(s) used to separate the substrings in the * returned <code>String</code>. If this parameter is null, * the <i>,</i> character will be used as delimiter char. * * @return The processed <code>String</code>. * @see #split(String, String, int, int) */ public static String join(final List<? extends Object> values, final String delimiter) { if(values==null || values.size() == 0) { return EMPTY; } return ArrayUtils.join(values.toArray(new Object[values.size()]), delimiter); } /** * Creates a zero-based, one-dimensional array containing a specified number * of substrings. per default, binary comperation is used without any result limit. * * @param text * A <code>String</code> containing substrings and delimiters. * If expression is a zero-length string(""), Split returns an * empty array, that is, an array with no elements and no data. * @param delimiter * A <code>String</code> used to identify the substring limits. * If this parameter is null, the whole String will be returned within an * array with a size of 1. * </ul> * @return The splitted <code>String</code> array. All result * @see #join(Object[], String) */ public static List<String> split(final String text, final String delimiter) { return split(text, delimiter, -1, UtilConstants.COMPARE_BINARY); } /** * Creates a zero-based, one-dimensional array containing a specified number * of substrings. * * @param text * A <code>String</code> containing substrings and delimiters. * If expression is a zero-length string(""), Split returns an * empty array, that is, an array with no elements and no data. * @param delimiter * A <code>String</code> used to identify the substring limits. * If this parameter is null, the whole String will be returned within an * array with a size of 1. * @param limit * Number of substrings to be returned; -1 indicates that all * substrings are returned. * @param compare * Specifies the string comparison to use. * <ul> * <li>0 = vbBinaryCompare - Perform a binary comparison (case * sensitive)</li> * <li>1 = vbTextCompare - Perform a textual comparison (case * insensitive)</li> * </ul> * @return The splitted <code>String</code> array. All result * @see #join(Object[], String) */ public static List<String> split(final String text, final String delimiter, final int limit, final int compare) { if (compare > UtilConstants.COMPARE_TEXT || compare < UtilConstants.COMPARE_BINARY) { throw new RuntimeException("Mode not supported"); } if (text == null) { return null; } final ArrayList<String> list = new InternalStringList(); if (delimiter == null) { list.add(text); return list; } if (delimiter.length() == 0) { return list; //empty list } if(limit == 0) { return list; //empty list } final String lowerCaseDelimiter = delimiter.toLowerCase(); String lowerCaseExpression = text.toLowerCase(); String textValue = text; //create a copy for the text parameter to work with it. while ((compare == UtilConstants.COMPARE_TEXT && !(lowerCaseExpression.indexOf(lowerCaseDelimiter) == -1)) || (compare == UtilConstants.COMPARE_BINARY && !(textValue.indexOf(delimiter) == -1))) { int index = -1; if (compare == UtilConstants.COMPARE_BINARY) { index = textValue.indexOf(delimiter); } else { index = lowerCaseExpression.indexOf(lowerCaseDelimiter); } list.add(textValue.substring(0, index)); textValue = textValue.substring(index + delimiter.length()); lowerCaseExpression = lowerCaseExpression.substring(index + lowerCaseDelimiter.length()); if(limit!=-1 && list.size() == limit) { break; } } if(limit == -1 || list.size() < limit) { list.add(textValue); } return list; } /** * convenience method and does the same as the <code>{@link #split(String, String, int, int)}</code> but * with UtilConstants.COMPARE_BINARY as compare parameter. * * @see ##split(String, String, int, int) */ public static List<String> split(final String text, final String delimiter, final int limit) { return split(text, delimiter, limit, UtilConstants.COMPARE_BINARY); } /** * Fast split with a character only separator. * @param text The text to be splitted. * @param separator The separator. * @return The splitted values. */ public static List<String> split(String text, char separator) { if(text == null) { return Collections.emptyList(); } ArrayList<String> result = new ArrayList<>(StringUtil.occurrence(text, String.valueOf(separator), UtilConstants.COMPARE_BINARY) + 1); int last = 0; for (int i = 0; i < text.length(); i++) { char c = text.charAt(i); if(c == separator) { char[] separated = new char[i - last]; text.getChars(last, i, separated, 0); result.add(String.valueOf(separated)); last = i+1; } } char[] separated = new char[text.length() - last]; if(separated.length > 0 || text.isEmpty() || text.charAt(text.length()-1) == separator) { text.getChars(last, text.length(), separated, 0); result.add(String.valueOf(separated)); } return result; } /** * Splits the given <code>text</code> into a string array where each * array entry has a string length, specified with the <code>length</code> parameter. * If the rest of the string is smaller than the specified <code>length</code>, the last * entry will be filled up with whitespace's. For example: * <br><br> * <pre><code> * chunkSplit("teststring", 3) * returns: String[] {"tes", "tst","rin","g "} * </code></pre> * * @param text The string to be splitted * @param length The length of each string in the result. * @return A string array where each string has the given length. */ public static List<String> chunkSplit(final String text, int length) { if(text==null || text.length()==0) { return new ArrayList<String>(0); } if(length<=0) { throw new RuntimeException("length for chunk splitting must be larger than 0 - given value is: '" + length+"'"); } List<String> result = new ArrayList<>( (int)MathUtils.roundUp((double)text.length()/(double)length, 0) ); StringBuilder buffer = new StringBuilder(length); for (int i = 1; i <= text.length(); i++) { buffer.append(text.charAt(i-1)); if(i>0 && i%length==0) { result.add(buffer.toString()); buffer = new StringBuilder(length); } } if(buffer.length()>0) { String lastEntry = StringUtil.padRight(buffer.toString(), length); result.add(lastEntry); } return result; } public static <T>List<T> filter(List<T> values, final List<?> match, final boolean include, final int compare, final int searchType) { ArrayList<T> result = new ArrayList<>(values.size()); for (Object o : match) { List<T> filtered = filter(values, o, include, compare, searchType); result.addAll(filtered); } return distinct(result, compare); } /** * Dos the same as the filter(String[], String, boolean, int, boolean) * method but uses VBConstants.SEARCH_DEFAULT per default for the searchType parameter. * * @see #filter(String[], String, boolean, int, boolean) * @return The filtered array. */ public static <T>List<T> filter(final List<T> values, final Object filterValue, final boolean include, final int compare) { return filter(values, filterValue, include, compare, UtilConstants.SEARCH_DEFAULT); } /** * The Filter function returns a zero-based array that contains a subset of * a <code>String</code> array based on a filter criteria. * * @param values * A one-dimensional array of <code>String</code>s to be * searched. The data type of this array specifies the * array data type for the result. * @param match * The <code>Object</code> to search for. This can also be an array. Each value of the array * will be used as filter value. * @param include * A Boolean value that indicates whether to return the * substrings that include or exclude value. True returns the * subset of the array that contains value as a substring. False * returns the subset of the array that does not contain value as * a substring. * @param compare * Specifies the string comparison to use. * <ul> * <li>0 = VBConstants.COMPARE_BINARY - Perform a binary * comparison (case sensitive) using the equals method of each object.</li> * <li>1 = VBConstants.COMPARE_TEXT - Perform a textual * comparison (case insensitive). Using text comparison, the * <code>toString()</code> method on the given object value * will be used for comparison.</li> * </ul> * @param searchType * The searchType parameter specifies how to use the search object specified * with the match parameter. Possible values are: * <UL> * <LI>0 = VBConstants.SEARCH_DEFAULT - performs a simple equals comperation to the <code>match</code> object.</LI> * <LI>1 = VBConstants.SEARCH_REGEXP - use the value specified in the parameter named <code>match</code> as regular expression.</LI> * <LI>2 = VBConstants.SEARCH_LIKE - use the value specified in the parameter named <code>match</code> like the LIKE operator.</LI> * </UL> * * Using VBConstants.SEARCH_REGEXP or VBConstants.SEARCH_LIKE, * the <code>toString()</code> method on the given * object value will be used for regexp. or like matching. * @return A new, filtered array instance. */ @SuppressWarnings("unchecked") public static <T>List<T> filter(List<T> values, final Object match, final boolean include, final int compare, final int searchType) { //test the comperation mode if (compare > UtilConstants.COMPARE_TEXT || compare < UtilConstants.COMPARE_BINARY) { throw new RuntimeException("Mode not supported"); } //test the array for null. if (values==null) { throw new RuntimeException("Array is null"); } //test if the filterValue is an array Object[] processFilterValue; if ( ArrayUtils.isArray(match) ) { processFilterValue = (Object[]) match; } else { processFilterValue = new Object[] {match}; } ArrayList<T> result = new ArrayList<>(); //loop all filter values for (int i = 0; i < processFilterValue.length; i++) { //loop each entry of the input array for (T next : values) { boolean found; if(next instanceof List) { //if the value to be compared is an array, filter and add it. result.add((T) filter((List<?>) next, match, include, compare, searchType)); } else { // compare process if (searchType == UtilConstants.SEARCH_REGEXP) { if (compare == UtilConstants.COMPARE_TEXT) { String pattern = String.valueOf(processFilterValue[i]).toLowerCase(); Pattern p = Pattern.compile(pattern); Matcher m = p.matcher(String.valueOf(next).toLowerCase()); found = m.matches(); } else { String pattern = String.valueOf(processFilterValue[i]); Pattern p = Pattern.compile(pattern); Matcher m = p.matcher(String.valueOf(next)); found = m.matches(); } } else { if (compare == UtilConstants.COMPARE_TEXT) { if (String.valueOf(next).equalsIgnoreCase(String.valueOf(processFilterValue[i]))) { found = true; } else { found = false; } } else { if (next.equals(processFilterValue[i]) || next == processFilterValue[i]) { found = true; } else { found = false; } } } // add process if (found && include) { result.add(next); } else if (!found && !include) { result.add(next); } } } // end inner loop } // end outer loop return result; } /** * Merges the two array given with the arguments <code>first</code> and * <code>second</code>. * * @param first * An array to be merged with the array given with the parameter * <code>second</code>. The data type of the first array specifies the * array data type for the result. * @param second * An array to be merged with the array given with the parameter * <code>first</code>. * @return The merged array. Never will return null. * * @throws RuntimeException if the array given with the first parameter is not an instance of the array given with the second parameter. */ public static <T>List<T> union(final List<T> first, final List<T> second) { //handle null values if (first==null && second==null) { return new ArrayList<T>(0); } else if (first==null) { return second; } else if (second==null) { return first; } return new CompoundList<T>(first, second); } /** * Determine all entries existing in both given arrays and creates a new * array which contains all duplicate entries. * * @param first * A one dimensional array to be searched for entries exiting in the array given * with the <code>second</code> parameter. The data type of the first array specifies the * array data type for the result. * @param second * A one dimensional array to be searched for entries exiting in the array given * with the <code>first</code> parameter. If this parameter is null, the first array * will be searched for duplicate values within it self. * @param compare * Specifies the string comparison to use. * <ul> * <li>0 = VBConstants.vbBinaryCompare - Perform a binary comparison (case sensitive)</li> * <li>1 = VBConstants.vbTextCompare - Perform a textual comparison (case insensitive)</li> * </ul> * * @return A new array containing all duplicate entries. */ public static <T>List<T> duplicates(final List<T> first, final List<T> second, final int compare) { //test the comperation mode if (compare > UtilConstants.COMPARE_TEXT || compare < UtilConstants.COMPARE_BINARY) { throw new RuntimeException("Mode not supported"); } if (second==null) { return duplicates(first, compare); } ArrayList<T> arrayList = new ArrayList<>(); for (T firstNext : first) { int count = 0; for (T secondNext : second) { //test if equals if (compare == UtilConstants.COMPARE_BINARY) { if (firstNext.equals(secondNext)) { count++; } } else { //Text compare if (String.valueOf(firstNext).equalsIgnoreCase(String.valueOf(secondNext))) { count++; } } } if (count > 0 && !arrayList.contains(firstNext)) { arrayList.add(firstNext); } } return arrayList; } /** * Searches for duplicate entries within the given array. * * @param values The array which should be searched for duplicates. * @param compare * Specifies the string comparison to use. * <ul> * <li>0 = VBConstants.vbBinaryCompare - Perform a binary comparison (case sensitive)</li> * <li>1 = VBConstants.vbTextCompare - Perform a textual comparison (case insensitive)</li> * </ul> * @return An array with the type of the array given with the values parameter containing all duplicate entries (but only one of the duplicate entries). */ public static <T>List<T> duplicates(final List<T> values, final int compare) { List<T> result = new ArrayList<>(values.size()); Iterator<T> iterator = values.iterator(); for (int i = 0; iterator.hasNext(); i++) { T next = iterator.next(); if ( indexOf(values, next, compare, UtilConstants.SEARCH_DEFAULT)!=i ) { result.add(next); } } //if there're more than two duplicate values, there will be some more than only one entry. result = distinct(result, compare); return result; } /** * Determine all entries that did not existing in both given arrays and * creates a new one which contains all non redundant entries. * * @param first * A one dimensional array to be searched for entries not exiting in the array given * with the <code>second</code> parameter. The data type of the first array specifies the * array data type for the result. * @param second * A one dimensional array to be searched for entries not exiting in the array given * with the <code>first</code> parameter. * @param compare * Specifies the string comparison to use. * <ul> * <li>0 = VBConstants.vbBinaryCompare - Perform a binary comparison (case sensitive)</li> * <li>1 = VBConstants.vbTextCompare - Perform a textual comparison (case insensitive)</li> * </ul> * @return A new array containing all non redundant entries. */ public static <T>List<T> difference(final List<T> first, final List<T> second, final int compare) { //test the comperation mode if (compare > UtilConstants.COMPARE_TEXT || compare < UtilConstants.COMPARE_BINARY) { throw new RuntimeException("Mode not supported"); } List<T> arrayList = new ArrayList<>(); List<T> duplicateArray = duplicates(first, second, compare); arrayList=copyDifference(first, arrayList, duplicateArray, compare); arrayList=copyDifference(second, arrayList, duplicateArray, compare); //create the result array return arrayList; } public static <T>List<T> difference(final List<T> first, final List<T> second) { return difference(first, second, UtilConstants.COMPARE_BINARY); } private static <T>List<T> copyDifference(final List<T> object, final List<T> previousValues, final List<T> duplicateArray, final int compare) { Iterator<T> iterator = object.iterator(); while (iterator.hasNext()) { T next = iterator.next(); int count = 0; for (T t : duplicateArray) { if (compare == UtilConstants.COMPARE_BINARY) { if (next.equals(t)) { count++; } } else { if (String.valueOf(next).equalsIgnoreCase(String.valueOf(t))) { count++; } } } if (count == 0 && !previousValues.contains(next)) { previousValues.add(next); } } return previousValues; } /** * Returns the index of the first occurrence of a value in a n'th-dimensional Array specified with the <code>values</code> parameter. * If the search value is found in a n'th dimension of the <code>values</code> array, the position for the array which contains * the value, in the first dimension is returned. * <br><br> * This method is using the search type <code>VBConstants.SEARCH_DEFAULT</code>. * * @param values The array to be searched for the desired value. * @param match The <code>Object</code> to be searched. * @param compare * Specifies the string comparison to use. * <ul> * <li>0 = VBConstants.vbBinaryCompare - Perform a binary comparison (case sensitive)</li> * <li>1 = VBConstants.vbTextCompare - Perform a textual comparison (case insensitive)</li> * </ul> * * @return The index for the found <code>match</code> in the <code>values</code> array. */ public static int indexOf(final List<Object> values, final Object match, final int compare) { return indexOf(values, match, compare, UtilConstants.SEARCH_DEFAULT); } /** * Returns the index of the first occurrence of a value in a n'th-dimensional Array specified with the <code>values</code> parameter. * If the search value is found in a n'th dimension of the <code>values</code> array, the position for the array which contains * the value, in the first dimension is returned. * * @param values The array to be searched for the desired value. * @param match The <code>Object</code> to be searched. * @param compare * Specifies the string comparison to use. * <ul> * <li>0 = VBConstants.vbBinaryCompare - Perform a binary comparison (case sensitive)</li> * <li>1 = VBConstants.vbTextCompare - Perform a textual comparison (case insensitive)</li> * </ul> * @param searchType * The searchType parameter specifies how to use the search object specified * with the match parameter. Possible values are: * <UL> * <LI>0 = VBConstants.SEARCH_DEFAULT - performs a simple equals comperation to the <code>match</code> object.</LI> * <LI>1 = VBConstants.SEARCH_REGEXP - use the value specified in the parameter named <code>match</code> as regular expression.</LI> * <LI>2 = VBConstants.SEARCH_LIKE - use the value specified in the parameter named <code>match</code> like the LIKE operator.</LI> * </UL> * * Using VBConstants.SEARCH_REGEXP or VBConstants.SEARCH_LIKE, * the <code>toString()</code> method on the given * object value will be used for regexp. or like matching. * * * @return The index for the found <code>match</code> in the <code>values</code> array. */ @SuppressWarnings({ "unchecked", "rawtypes" }) public static int indexOf(final List<?> values, final Object match, final int compare, final int searchType) { //test the comperation mode if (compare > UtilConstants.COMPARE_TEXT || compare < UtilConstants.COMPARE_BINARY) { throw new RuntimeException("Mode not supported"); } //test the array for null. if (values==null) { throw new RuntimeException("Values is null"); } //test if the filterValue is an array Object[] processFilterValue; if ( ArrayUtils.isArray(match) ) { processFilterValue = (Object[]) match; } else { processFilterValue = new Object[] {match}; } //loop all values for (int i = 0; i < processFilterValue.length; i++) { boolean canCompareTo = true; //loop each entry of the input array Iterator<?> iterator = values.iterator(); for (int j = 0; iterator.hasNext(); j++) { final Object next = iterator.next(); if(next instanceof List) { //if the value to be compared is an array, filter and add it. if (indexOf((List<?>)next, match, compare, searchType)!=-1) { return j; } } else { // compare process if (searchType == UtilConstants.SEARCH_REGEXP) { if (compare == UtilConstants.COMPARE_TEXT) { String pattern = String.valueOf(processFilterValue[i]).trim().toLowerCase(); Pattern p = Pattern.compile(pattern); Matcher m = p.matcher(String.valueOf(next).toLowerCase()); if (m.matches()) { return j; } } else { String pattern = String.valueOf(String.valueOf(processFilterValue[i])).toLowerCase(); Pattern p = Pattern.compile(pattern); Matcher m = p.matcher(String.valueOf(next)); if (m.matches()) { return j; } } } else { if (next==null && match==null) { return j; } else if (next==null && processFilterValue[i]==null) { return j; } else if (next==null && processFilterValue[i]!=null) { continue; } if (compare == UtilConstants.COMPARE_TEXT) { if (String.valueOf(next).trim().equalsIgnoreCase(String.valueOf(processFilterValue[i]).trim())) { return j; } } else { if(canCompareTo && next instanceof Comparable && processFilterValue[i] instanceof Comparable) { try { if(((Comparable)next).compareTo(processFilterValue[i]) == 0) { return j; } } catch (Exception e) { //could happens if the Comparable is generic and the class types did not match. if (next.equals(processFilterValue[i]) || next == processFilterValue[i]) { return j; } canCompareTo = false; } } else { if (next.equals(processFilterValue[i]) || next == processFilterValue[i]) { return j; } } } } } } // end inner loop } // end outer loop return -1; } /** * Removes all repetioned entries from the array specified with the <code>values</code> * parameter. * * @param values The array where duplicated entries should be removed. * @param compare * Specifies the string comparison to use. * <ul> * <li>0 = vbBinaryCompare - Perform a binary comparison (case * sensitive)</li> * <li>1 = vbTextCompare - Perform a textual comparison (case * insensitive)</li> * </ul> * @return A new created array without any duplicated entries. */ public static <T>List<T> distinct(final List<T> values, final int compare) { final ArrayList<T> result = new ArrayList<>(values.size()); final Iterator<T> iterator = values.iterator(); for (int i = 0; iterator.hasNext(); i++) { final T next = iterator.next(); if ( indexOf(values, next, compare, UtilConstants.SEARCH_DEFAULT) == i ) { result.add(next); } } return result; } public static <T>List<T> distinct(final List<T> values) { return distinct(values, UtilConstants.COMPARE_BINARY); } /** * Extracts these part of the <code>values</code> array which is defined * between <code>start</code> and <code>end</code>. The first array entry * starts with 0. * <br><br> * If the start or end parameter ist out of range, the range will be resized * to a valid value. No <code>ArrayIndexOutOfRange</code> exception is thrown. * * @param values The array to be extracted. * @param start The start position. * @param end The end position. * @return A new array instance with the same type as the array specified * with the <code>values</code> parameter which contains all values between * <code>start</code> and <code>end</code>. */ public static <T>List<T> extract(final List<T> values, int start, int end) { //create the resized array List<T> result = new ArrayList<>(end); for(int i = start; i < values.size() && i < end; i++) { result.add(values.get(i)); } return result; } /** * Search for the value specified with the <code>match</code> parameter and * return the position for the first found occurrence or -1 if no match was found. * * @param values The array to be searched. * @param match The value to be searched in the array. This can be also an array with values to be searched. * @param compare * Specifies the string comparison to use. * <ul> * <li>0 = VBConstants.vbBinaryCompare - Perform a binary comparison (case sensitive)</li> * <li>1 = VBConstants.vbTextCompare - Perform a textual comparison (case insensitive)</li> * </ul> * @param searchType * The searchType parameter specifies how to use the search object specified * with the match parameter. Possible values are: * <UL> * <LI>0 = VBConstants.SEARCH_DEFAULT - performs a simple equals comperation to the <code>match</code> object.</LI> * <LI>1 = VBConstants.SEARCH_REGEXP - use the value specified in the parameter named <code>match</code> as regular expression.</LI> * <LI>2 = VBConstants.SEARCH_LIKE - use the value specified in the parameter named <code>match</code> like the LIKE operator.</LI> * </UL> * * Using VBConstants.SEARCH_REGEXP or VBConstants.SEARCH_LIKE, * the <code>toString()</code> method on the given * object value will be used for regexp. or like matching. * * @return The index of the first found occurrence or -1 if no match was found. */ public static boolean exists(final List<?> values, final Object match, final int compare, final int searchType) { return indexOf(values, match, compare, searchType)!=-1; } /** * Sets the given value to the desired index of the given list. If the size of the list * isn't large enough, the list will be filled with <code>null</code> values. * @param <T> * @param values The list where the value should be set to. * @param value The value to be set into the list * @param idx The list index where the value should be inserted into the list. */ public static <T>void set(final List<T> values, T value, int idx) { if(values.size() > idx) { values.set(idx, value); } else { for (int i = values.size(); i <= idx; i++) { values.add(null); } set(values, value, idx); } } /** * Get a value from the given {@link List}. * @param values The list containing the values where a value should be returned from. * @param idx The index of the value to be returned in the given list. * @return The value from the index or <code>null</code> if the list is smaller than the * index requires. */ public static <T>T get(final List<T> values, int idx) { if(values.size() > idx) { return values.get(idx); } return null; } /** * gets the first entry from the given list. If the list is <code>null</code> * or empty <code>null</code> is returned. * @param object The list where the first entry should be fetched from. * @return The desired list entry or <code>null</code>. */ public static <T> T first(List<T> object) { if(object != null && !object.isEmpty()) { return object.get(0); } return null; } /** * Creates a String from the given string list with indices for the split points. * This list can be converted into a List very fast by using the {@link #fromIndexString(String, char)} * method. * * @param list The list to be converted into a String. * @param separator The separator char that must not be contained by the strings in the given list. * @return The desired String. * @see #fromIndexString(String, char) */ public static String toIndexedString(final Collection<String> list, final char separator) { if(Character.isDigit(separator)) { throw new IllegalArgumentException("The separator must not be a number"); } final char EOL = '\n'; // end of list marker final StringBuilder indices = new StringBuilder(list.size() * 4); final StringBuilder strings = new StringBuilder(list.size() * 16); int index = 0; for (final String element : list) { int length = element.length(); strings.append(element); strings.append(separator); indices.append(index + length); indices.append(separator); index += length + 1; } return indices.append(EOL).append(strings).toString(); } /** * Creates a List from the given string. This String must have the split indices at the front. * Use {@link #toIndexedString(List, char)} to create those string. * * @param indexString The index string to be parsed. * @param separator The separator which separated the entries. * @return The desired list * @see #toIndexedString(List, char) */ public static List<String> fromIndexString(final String indexedListString, final char separator) { final ArrayList<String> list = new ArrayList<>(); final char EOL = '\n'; // end of list marker final int length = indexedListString.length(); final int indexEnd = indexedListString.indexOf(EOL) + 1; // end of the index area int start = 0; int prevIndex = Integer.valueOf(0); for (int i = 0; i < length; i++) { if(indexedListString.charAt(i) == separator) { int index = Integer.valueOf(indexedListString.substring(start, i)).intValue(); int indexModifier = start == 0 ? 0 : 1; // the first entry did not have a separator String entry = indexedListString.substring(prevIndex + indexModifier + indexEnd, index + indexEnd); prevIndex = index; start = i + 1; list.add(entry); } else if(indexedListString.charAt(i) == EOL) { break; // no more entries } } return list; } public static boolean isNotEmpty(List<?> l) { return !isEmpty(l); } public static boolean isEmpty(List<?> l) { return l == null || l.isEmpty(); } }