propel.core.utils.StringUtils.java Source code

Java tutorial

Introduction

Here is the source code for propel.core.utils.StringUtils.java

Source

// /////////////////////////////////////////////////////////
// This file is part of Propel.
//
// Propel is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Propel is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Propel. If not, see <http://www.gnu.org/licenses/>.
// /////////////////////////////////////////////////////////
// Authored by: Nikolaos Tountas -> salam.kaser-at-gmail.com
// /////////////////////////////////////////////////////////
package propel.core.utils;

import static propel.core.functional.predicates.Strings.isNotNullOrEmpty;
import static propel.core.functional.predicates.Strings.lengthEquals;
import static propel.core.functional.projections.Strings.charAt;

import java.math.BigDecimal;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.text.Collator;
import java.text.DateFormat;
import java.text.DecimalFormatSymbols;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.UUID;

import lombok.Validate;
import lombok.Validate.NotNull;
import lombok.val;

import org.joda.time.Duration;
import org.joda.time.LocalDateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.DateTimeFormatterBuilder;
import org.joda.time.format.DateTimeParser;
import org.joda.time.format.ISODateTimeFormat;

import propel.core.TryResult;
import propel.core.collections.lists.ReifiedArrayList;
import propel.core.collections.lists.ReifiedList;
import propel.core.common.CONSTANT;
import propel.core.userTypes.Int128;
import propel.core.userTypes.UnsignedByte;
import propel.core.userTypes.UnsignedInteger;
import propel.core.userTypes.UnsignedLong;
import propel.core.userTypes.UnsignedShort;

/**
 * Provides helper functionality for Strings and char arrays.
 */
public final class StringUtils {
    /**
     * The current locale of the JVM
     */
    public static final Locale CURRENT_LOCALE = Locale.getDefault();
    /**
     * An invariant locale across JVMs
     */
    public static final Locale INVARIANT_LOCALE = Locale.US;
    /**
     * The current locale's collator
     */
    public static final Collator CURRENT_LOCALE_COLLATOR = Collator.getInstance(CURRENT_LOCALE);
    /**
     * The invariant locale's collator
     */
    public static final Collator INVARIANT_LOCALE_COLLATOR = Collator.getInstance(INVARIANT_LOCALE);
    private static final DecimalFormatSymbols CURRENT_DECIMAL_SYMBOLS = new DecimalFormatSymbols(CURRENT_LOCALE);
    /**
     * Current locale decimal separator symbol
     */
    public static final char DECIMAL_SEPARATOR = CURRENT_DECIMAL_SYMBOLS.getDecimalSeparator();
    /**
     * Current locale decimal grouping symbol
     */
    public static final char GROUPING_SEPARATOR = CURRENT_DECIMAL_SYMBOLS.getGroupingSeparator();
    private static final Duration MIN_DURATION = new Duration(Long.MIN_VALUE);
    private static final Duration MAX_DURATION = new Duration(Long.MAX_VALUE);
    private static final LocalDateTime MIN_DATETIME = new LocalDateTime(1, 1, 1, 0, 0, 0); // 1/1/0001 00:00:00
    private static final LocalDateTime MAX_DATETIME = new LocalDateTime(9999, 12, 31, 23, 59, 59); // 31/12/9999 23:59:59
    /**
     * ISO standard date/time formatters (composite class)
     */
    public static final DateTimeFormatter STANDARD_FORMATTERS = (new DateTimeFormatterBuilder())
            .append(null, createCommonDateTimeParsers()).toFormatter();

    /**
     * Returns a character range from start (inclusive) to end (exclusive).
     * 
     * @throws IllegalArgumentException When the end is before start
     */
    public static char[] charRange(char start, char end) {
        int length = (int) end - (int) start;
        if (length < 0)
            throw new IllegalArgumentException("start=" + (int) start + " end=" + (int) end);

        char[] result = new char[length];

        int index = 0;
        for (char ch = start; ch < end; ch++)
            result[index++] = ch;

        return result;
    }

    /**
     * Returns a character range from start (inclusive) to end (exclusive).
     * 
     * @throws IllegalArgumentException When the end is before start
     */
    public static char[] charRange(int start, int end) {
        return charRange((char) start, (char) end);
    }

    /**
     * Compares two Strings using a CurrentLocale string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    public static int compare(String a, String b) {
        return compare(a, b, StringComparison.CurrentLocale);
    }

    /**
     * Compares two Strings using the specified string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static int compare(@NotNull final String a, @NotNull final String b, StringComparison stringComparison) {
        switch (stringComparison) {
        case CurrentLocale:
            return compareLocaleSensitive(a, b, CURRENT_LOCALE, CURRENT_LOCALE_COLLATOR, true);
        case CurrentLocaleIgnoreCase:
            return compareLocaleSensitive(a, b, CURRENT_LOCALE, CURRENT_LOCALE_COLLATOR, false);
        case InvariantLocale:
            return compareLocaleSensitive(a, b, INVARIANT_LOCALE, INVARIANT_LOCALE_COLLATOR, true);
        case InvariantLocaleIgnoreCase:
            return compareLocaleSensitive(a, b, INVARIANT_LOCALE, INVARIANT_LOCALE_COLLATOR, false);
        case Ordinal:
            return compareOrdinal(a, b, true);
        case OrdinalIgnoreCase:
            return compareOrdinal(a, b, false);
        default:
            throw new IllegalArgumentException(
                    "stringComparison has an unexpected value: " + stringComparison.toString());
        }
    }

    /**
     * Comparison function, uses higher performance locale-aware comparison, uses existing collator to avoid creating one every time.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static int compare(@NotNull final String a, @NotNull final String b, Locale locale, Collator collator,
            boolean caseSensitive) {
        return compareLocaleSensitive(a, b, locale, collator, caseSensitive);
    }

    /**
     * Locale-aware comparison.
     * 
     * @throws NullPointerException An argument is null
     */
    @Validate
    private static int compareLocaleSensitive(String a, String b, @NotNull final Locale locale,
            @NotNull final Collator collator, boolean caseSensitive) {
        if (!caseSensitive) {
            a = a.toLowerCase(locale);
            b = b.toLowerCase(locale);
        }

        return collator.compare(a, b);
    }

    /**
     * Compares two strings lexicographically
     * 
     * @throws NullPointerException An argument is null
     */
    private static int compareOrdinal(String a, String b, boolean caseSensitive) {
        if (caseSensitive)
            return a.compareTo(b);
        else {
            // ordinal ignore case
            int len1 = a.length();
            int len2 = b.length();
            int lim = len1 < len2 ? len1 : len2;
            char v1[] = a.toCharArray();
            char v2[] = b.toCharArray();

            int i = 0;
            while (i < lim) {
                char c1 = v1[i];
                char c2 = v2[i];

                // letters
                if ((c1 >= 65 && c1 <= 90) || (c1 >= 97 && c1 <= 122)) {
                    c1 = Character.toLowerCase(c1);
                    c2 = Character.toLowerCase(c2);
                }

                if (c1 != c2)
                    return c1 - c2;

                i++;
            }

            return len1 - len2;
        }
    }

    /**
     * Concatenates a collection of strings into a single string. Ignores null items.
     * 
     * @throws NullPointerException An argument is null.
     */
    public static String concat(Iterable<String> values) {
        return delimit(values, CONSTANT.EMPTY_STRING, null);
    }

    /**
     * Concatenates a collection of strings into a single string. Substitutes null items with the null-replacement value provided, if not
     * null.
     * 
     * @throws NullPointerException The values argument is null.
     */
    public static String concat(Iterable<String> values, String nullReplacementValue) {
        return delimit(values, CONSTANT.EMPTY_STRING, nullReplacementValue);
    }

    /**
     * Concatenates a collection of strings into a single string. Ignores null items.
     * 
     * @throws NullPointerException An argument is null.
     */
    public static String concat(String[] values) {
        return delimit(values, CONSTANT.EMPTY_STRING, null);
    }

    /**
     * Concatenates a collection of strings into a single string. Substitutes null items with the null-replacement value provided, if not
     * null.
     * 
     * @throws NullPointerException The values argument is null.
     */
    public static String concat(String[] values, String nullReplacementValue) {
        return delimit(values, CONSTANT.EMPTY_STRING, nullReplacementValue);
    }

    /**
     * Concatenates the given chars. Returns String.Empty if an empty collection was provided.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static String concat(@NotNull final char[] values) {
        return new String(values);
    }

    /**
     * Concatenates the given chars. Returns an empty array if no chars are found.
     * 
     * @throws NullPointerException When the values or one of its arguments is null.
     */
    @Validate
    public static char[] concat(@NotNull final char[]... values) {
        int count = 0;
        for (char[] arr : values) {
            if (arr == null)
                throw new NullPointerException("Item of values");

            count += arr.length;
        }

        char[] result = new char[count];

        int index = 0;
        for (char[] arr : values) {
            System.arraycopy(arr, 0, result, index, arr.length);
            index += arr.length;
        }

        return result;
    }

    /**
     * Returns true if a char sequence contains a character
     * 
     * @throws NullPointerException When the sequence is null.
     */
    @Validate
    public static boolean contains(@NotNull final char[] sequence, char ch) {
        for (char c : sequence)
            if (c == ch)
                return true;

        return false;
    }

    /**
     * Returns true if a string contains a character
     * 
     * @throws NullPointerException When the sequence is null.
     */
    @Validate
    public static boolean contains(@NotNull final String value, char ch) {
        for (char c : value.toCharArray())
            if (c == ch)
                return true;

        return false;
    }

    /**
     * Returns true if the part is contained in the value. Uses CurrentLocale string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    public static boolean contains(String value, String part) {
        return contains(value, part, StringComparison.CurrentLocale);
    }

    /**
     * Returns true if the part is contained in the value. Uses the specified string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    public static boolean contains(String value, String part, StringComparison stringComparison) {
        return indexOf(value, part, 0, value.length(), stringComparison) >= 0;
    }

    /**
     * Returns true if the part is contained in the value. Uses culture-aware higher performance string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    public static boolean contains(String value, String part, Locale locale, Collator collator,
            boolean caseSensitive) {
        return indexOf(value, part, 0, value.length(), locale, collator, caseSensitive) >= 0;
    }

    /**
     * Returns true if the value is contained in the collection of values. Uses the specified string comparison mode.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static boolean contains(@NotNull final Iterable<String> values, String value,
            StringComparison stringComparison) {
        if (value == null)
            return Linq.contains(values, null);
        else
            for (String val : values)
                if (equal(value, val, stringComparison))
                    return true;

        return false;
    }

    /**
     * Returns true if the value is contained in the collection of values. Uses culture-aware higher performance string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static boolean contains(@NotNull final Iterable<String> values, String value, Locale locale,
            Collator collator, boolean caseSensitive) {
        if (value == null)
            return Linq.contains(values, null);
        else
            for (String val : values)
                if (equal(value, val, locale, collator, caseSensitive))
                    return true;

        return false;
    }

    /**
     * Returns true if the value is contained in the collection of values. Uses the specified string comparison mode.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static boolean contains(@NotNull final String[] values, String value,
            StringComparison stringComparison) {
        if (value == null)
            return Linq.contains(values, null);
        else
            for (String val : values)
                if (equal(value, val, stringComparison))
                    return true;

        return false;
    }

    /**
     * Returns true if the value is contained in the collection of values. Uses culture-aware higher performance string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static boolean contains(@NotNull final String[] values, String value, Locale locale, Collator collator,
            boolean caseSensitive) {
        if (value == null)
            return Linq.contains(values, null);
        else
            for (String val : values)
                if (equal(value, val, locale, collator, caseSensitive))
                    return true;

        return false;
    }

    /**
     * Returns true if all characters are contained in a string. Order is not taken into consideration.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static boolean containsAll(@NotNull final String value, @NotNull final char[] characters) {
        for (char ch : characters)
            if (value.indexOf(ch) < 0)
                return false;

        return true;
    }

    /**
     * Returns true if all strings specified are contained in the value string. Order is not taken into consideration. Uses the CurrentLocale
     * StringComparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    public static boolean containsAll(String value, Iterable<String> parts) {
        return containsAll(value, parts, StringComparison.CurrentLocale);
    }

    /**
     * Returns true if all strings specified are contained in the value string. Order is not taken into consideration. If a part is null then
     * false is returned.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static boolean containsAll(@NotNull final String value, @NotNull final Iterable<String> parts,
            StringComparison stringComparison) {
        // check that all exist
        for (String part : parts)
            if (part == null)
                return false;
            else if (indexOf(value, part, 0, value.length(), stringComparison) < 0)
                return false;

        return true;
    }

    /**
     * Returns true if all strings specified are contained in the value string. Order is not taken into consideration. If a part is null then
     * false is returned. Uses culture-aware higher performance string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static boolean containsAll(@NotNull final String value, @NotNull final Iterable<String> parts,
            Locale locale, Collator collator, boolean caseSensitive) {
        // check that all exist
        for (String part : parts)
            if (part == null)
                return false;
            else if (indexOf(value, part, 0, value.length(), locale, collator, caseSensitive) < 0)
                return false;

        return true;
    }

    /**
     * Returns true if all items are contained in the values string collection. Order is not taken into consideration. You may use null
     * elements for both values and items. Uses CurrentLocale string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    public static boolean containsAll(Iterable<String> values, Iterable<String> items) {
        return containsAll(values, items, StringComparison.CurrentLocale);
    }

    /**
     * Returns true if all items are contained in the values string collection. Order is not taken into consideration. You may use null
     * elements for both values and items.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static boolean containsAll(@NotNull final Iterable<String> values, @NotNull final Iterable<String> items,
            StringComparison stringComparison) {
        for (String item : items) {
            boolean found = false;

            for (String value : values) {
                if (item == null && value == null)
                    continue;
                if (item == null || value == null)
                    return false;
                if (equal(item, value, stringComparison))
                    found = true;
            }

            if (!found)
                return false;
        }

        return true;
    }

    /**
     * Returns true if all items in items are contained in the values string collection. Order is not taken into consideration. You may use
     * null elements for values and items. Uses a culture-aware higher performance string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static boolean containsAll(@NotNull final Iterable<String> values, @NotNull final Iterable<String> items,
            Locale locale, Collator collator, boolean caseSensitive) {
        for (String item : items) {
            boolean found = false;

            for (String value : values) {
                if (item == null && value == null)
                    continue;
                if (item == null || value == null)
                    return false;
                if (equal(item, value, locale, collator, caseSensitive))
                    found = true;
            }

            if (!found)
                return false;
        }

        return true;
    }

    /**
     * Returns true if all characters are contained in a string. Order is not taken into consideration.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static boolean containsAny(@NotNull final String value, @NotNull final char[] characters) {
        for (char ch : characters)
            if (value.indexOf(ch) >= 0)
                return true;

        return false;
    }

    /**
     * Returns true if any of the strings are contained in a string. Order is not taken into consideration. Uses the CurrentLocale
     * StringComparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    public static boolean containsAny(String value, Iterable<String> parts) {
        return containsAny(value, parts, StringComparison.CurrentLocale);
    }

    /**
     * Returns true if any of the strings are contained in a string. Order is not taken into consideration. If a part is null then it is
     * ignored.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static boolean containsAny(@NotNull final String value, @NotNull final Iterable<String> parts,
            StringComparison stringComparison) {
        // check if parts contained
        for (String part : parts)
            if (part != null)
                if (contains(value, part, stringComparison))
                    return true;

        return false;
    }

    /**
     * Returns true if any of the strings are contained in a string. Order is not taken into consideration. If a part is null then it is
     * ignored. Uses a culture-aware higher performance string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static boolean containsAny(@NotNull final String value, @NotNull final Iterable<String> parts,
            Locale locale, Collator collator, boolean caseSensitive) {
        // check if parts contained
        for (String part : parts)
            if (part != null)
                if (contains(value, part, locale, collator, caseSensitive))
                    return true;

        return false;
    }

    /**
     * Returns true if any item in items is contained in the values string collection. Order is not taken into consideration. You may use null
     * elements for values and items.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static boolean containsAny(@NotNull final Iterable<String> values, @NotNull final Iterable<String> items,
            StringComparison stringComparison) {
        for (String item : items)
            for (String value : values) {
                if (item == null && value == null)
                    return true;
                if (item == null || value == null)
                    continue;
                if (equal(item, value, stringComparison))
                    return true;
            }

        return false;
    }

    /**
     * Returns true if any item in items is contained in the values string collection. Order is not taken into consideration. You may use null
     * elements for values and items. Uses a culture-aware higher performance string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static boolean containsAny(@NotNull final Iterable<String> values, @NotNull final Iterable<String> items,
            Locale locale, Collator collator, boolean caseSensitive) {
        for (String item : items)
            for (String value : values) {
                if (item == null && value == null)
                    return true;
                if (item == null || value == null)
                    continue;
                if (equal(item, value, locale, collator, caseSensitive))
                    return true;
            }

        return false;
    }

    /**
     * Similar to String.Substring
     * 
     * @throws NullPointerException An argument is null.
     * @throws IndexOutOfBoundsException An index is out of bounds.
     */
    @Validate
    public static String copy(@NotNull final String value, int startIndex, int endIndex) {
        if (startIndex < 0 || startIndex > endIndex)
            throw new IndexOutOfBoundsException("startIndex=" + startIndex + " endIndex=" + endIndex);
        if (endIndex > value.length())
            throw new IndexOutOfBoundsException("endIndex=" + endIndex + " length=" + value.length());

        return value.substring(startIndex, endIndex);
    }

    /**
     * Returns the number of occurences of a character in a character array.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static int count(@NotNull final char[] array, char ch) {
        int count = 0;
        for (char c : array)
            if (c == ch)
                count++;

        return count;
    }

    /**
     * Returns the number of occurences of a character in a string value.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static int count(@NotNull final String value, char character) {
        int result = 0;

        for (char s : value.toCharArray())
            if (s == character)
                result++;

        return result;
    }

    /**
     * Returns the number of occurences of a string element in a string value, using the CurrentLocale StringComparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    public static int count(String value, String element) {
        return count(value, element, StringComparison.CurrentLocale);
    }

    /**
     * Returns the number of occurences of a string element in a string value, using the specified string comparison type.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static int count(@NotNull final String value, @NotNull final String element,
            StringComparison stringComparison) {
        int valLen = value.length();

        if (valLen <= 0 || element.length() <= 0)
            return 0;

        int result = 0;
        int index = 0;

        while ((index = indexOf(value, element, index, valLen - index, stringComparison)) >= 0) {
            index++;
            result++;
        }

        return result;
    }

    /**
     * Returns the number of occurences of a string element in a string value, using the specified string comparison type. Uses a
     * culture-aware higher performance string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static int count(@NotNull final String value, @NotNull final String element, Locale locale,
            Collator collator, boolean caseSensitive) {
        int valLen = value.length();

        if (valLen <= 0 || element.length() <= 0)
            return 0;

        int result = 0;
        int index = 0;

        while ((index = indexOf(value, element, index, valLen - index, locale, collator, caseSensitive)) >= 0) {
            index++;
            result++;
        }

        return result;
    }

    /**
     * Performs a cropStart and cropEnd, returning the result
     * 
     * @throws NullPointerException An argument is null
     */
    public static String crop(String value, char except) {
        return cropStart(cropEnd(value, except), except);
    }

    /**
     * Performs a cropStart and cropEnd, returning the result
     * 
     * @throws NullPointerException An argument is null
     */
    public static String crop(String value, char[] except) {
        return cropStart(cropEnd(value, except), except);
    }

    /**
     * Crops all characters from the start of the given string, until the except character is encountered
     * 
     * @throws NullPointerException An argument is null
     */
    public static String cropStart(String value, char except) {
        return cropStart(value, new char[] { except });
    }

    /**
     * Crops all characters from the start of the given string, until a character is encountered which exists in the given exception array
     * 
     * @throws NullPointerException An argument is null
     */
    @Validate
    public static String cropStart(@NotNull final String value, @NotNull final char[] except) {
        int startIndex = 0;
        while (startIndex <= value.length() - 1 && !contains(except, value.charAt(startIndex)))
            startIndex++;

        return value.substring(startIndex);
    }

    /**
     * Crops all characters from the end of the given string, until the except character is encountered
     * 
     * @throws NullPointerException An argument is null
     */
    public static String cropEnd(String value, char except) {
        return cropEnd(value, new char[] { except });
    }

    /**
     * Crops all characters from the end of the given string, until a character is encountered which exists in the given exception array
     * 
     * @throws NullPointerException An argument is null
     */
    @Validate
    public static String cropEnd(@NotNull final String value, @NotNull final char[] except) {
        int endIndex = value.length() - 1;
        while (endIndex > 0 && !contains(except, value.charAt(endIndex)))
            endIndex--;

        return value.substring(0, endIndex + 1);
    }

    /**
     * Returns CR, LF or CRLF, depending on the frequency of line separators found in the given text data.
     * 
     * @throws NullPointerException An argument is null.
     */
    public static String detectLineSeparator(String text) {
        return detectLineSeparator(text, 2.0f);
    }

    /**
     * Returns CR, LF or CRLF, depending on the frequency of line separators found in the given text data. Accepts a ratio, to ensure the
     * decision is not swayed by CRs or LFs appearing randomly in the code. For example a value of 2.0f for a CR End-Of-Line (EOL) terminated
     * file would return CR if the file contains twice the number of CRs than the number of LFs.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static String detectLineSeparator(@NotNull final String text, float ratio) {
        int crs = count(text, CONSTANT.CR_CHAR);
        int lfs = count(text, CONSTANT.LF_CHAR);

        // multiply to ensure random CR/LFs used in source do not affect the outcome
        if (crs > lfs * ratio)
            return CONSTANT.CR;
        else if (lfs > crs * ratio)
            return CONSTANT.LF;
        else
            return CONSTANT.CRLF;
    }

    /**
     * Deletes the specified range of characters from the string.
     * 
     * @throws NullPointerException An argument is null.
     * @throws IndexOutOfBoundsException An index is out of bounds.
     */
    @Validate
    public static String delete(@NotNull final String value, int startIndex, int endIndex) {
        if (startIndex < 0 || startIndex > endIndex)
            throw new IndexOutOfBoundsException("startIndex=" + startIndex + " endIndex=" + endIndex);
        if (endIndex > value.length())
            throw new IndexOutOfBoundsException("endIndex=" + endIndex + " length=" + value.length());

        return value.substring(0, startIndex) + value.substring(endIndex);
    }

    /**
     * Concatenates the given values using their toString() method and appending the given delimiter between all values. Returns String.Empty
     * if an empty or null collection was provided. Ignores null collection items.
     * 
     * @throws NullPointerException An argument is null.
     */
    public static String delimit(Iterable<String> values, String delimiter) {
        return delimit(values, delimiter, null);
    }

    /**
     * Concatenates the given values using their toString() method and appending the given delimiter between all values. Returns String.Empty
     * if an empty or null collection was provided. Substitutes null items with a null-replacement value, if provided and is not null.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static String delimit(@NotNull final Iterable<String> values, @NotNull final String delimiter,
            String nullReplacementValue) {
        val sb = new StringBuilder(256);

        for (String value : values)
            if (value != null) {
                sb.append(value.toString());
                sb.append(delimiter);
            } else
            // append null replacement
            if (nullReplacementValue != null) {
                sb.append(nullReplacementValue);
                sb.append(delimiter);
            }

        if (sb.length() > 0)
            return sb.subSequence(0, sb.length() - delimiter.length()).toString();

        return CONSTANT.EMPTY_STRING;
    }

    /**
     * Concatenates the given values using their toString() method and appending the given delimiter between all values. Returns String.Empty
     * if an empty or null collection was provided. Ignores null collection items.
     * 
     * @throws NullPointerException An argument is null.
     */
    public static String delimit(String[] values, String delimiter) {
        return delimit(values, delimiter, null);
    }

    /**
     * Concatenates the given values using their toString() method and appending the given delimiter between all values. Returns String.Empty
     * if an empty or null collection was provided. Substitutes null items with a null-replacement value, if provided and is not null.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static String delimit(@NotNull final String[] values, @NotNull final String delimiter,
            String nullReplacementValue) {
        val sb = new StringBuilder(256);

        for (String value : values)
            if (value != null) {
                sb.append(value.toString());
                sb.append(delimiter);
            } else
            // append null replacement
            if (nullReplacementValue != null) {
                sb.append(nullReplacementValue);
                sb.append(delimiter);
            }

        if (sb.length() > 0)
            return sb.subSequence(0, sb.length() - delimiter.length()).toString();

        return CONSTANT.EMPTY_STRING;
    }

    /**
     * Concatenates the given chars with the given delimiter between all values. Returns String.Empty if an empty or null collection was
     * provided.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static String delimit(@NotNull final char[] values, @NotNull final String delimiter) {
        val sb = new StringBuilder(256);

        for (char value : values) {
            sb.append(value);
            sb.append(delimiter);
        }

        if (sb.length() > 0)
            return sb.subSequence(0, sb.length() - delimiter.length()).toString();

        return CONSTANT.EMPTY_STRING;
    }

    /**
     * Returns true if the value ends with a suffix.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static boolean endsWith(@NotNull final String value, char suffix) {
        if (value.length() == 0)
            return false;

        return value.charAt(value.length() - 1) == suffix;
    }

    /**
     * Returns true if a value ends with a suffix. Uses the CurrentLocale string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    public static boolean endsWith(String value, String suffix) {
        return endsWith(value, suffix, StringComparison.CurrentLocale);
    }

    /**
     * Returns true if a value ends with a suffix. Uses the specified string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    public static boolean endsWith(String value, String suffix, StringComparison stringComparison) {
        return lastIndexOf(value, suffix, value.length() - 1, 1, stringComparison) >= 0;
    }

    /**
     * Returns true if a value ends with a suffix. Uses a culture-aware higher performance string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    public static boolean endsWith(String value, String suffix, Locale locale, Collator collator,
            boolean caseSensitive) {
        return lastIndexOf(value, suffix, value.length() - 1, 1, locale, collator, caseSensitive) >= 0;
    }

    /**
     * Returns true if a value equals with another. Uses the CurrentLocale string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    public static boolean equal(String a, String b) {
        return equal(a, b, StringComparison.CurrentLocale);
    }

    /**
     * Returns true if a value equals with another. Uses the specified string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static boolean equal(@NotNull final String a, @NotNull final String b,
            StringComparison stringComparison) {
        switch (stringComparison) {
        case CurrentLocale:
            return equalLocaleSensitive(a, b, CURRENT_LOCALE, CURRENT_LOCALE_COLLATOR, true);
        case CurrentLocaleIgnoreCase:
            return equalLocaleSensitive(a, b, CURRENT_LOCALE, CURRENT_LOCALE_COLLATOR, false);
        case InvariantLocale:
            return equalLocaleSensitive(a, b, INVARIANT_LOCALE, INVARIANT_LOCALE_COLLATOR, true);
        case InvariantLocaleIgnoreCase:
            return equalLocaleSensitive(a, b, INVARIANT_LOCALE, INVARIANT_LOCALE_COLLATOR, false);
        case Ordinal:
            return equalOrdinal(a, b, true);
        case OrdinalIgnoreCase:
            return equalOrdinal(a, b, false);
        default:
            throw new IllegalArgumentException(
                    "stringComparison has an unexpected value: " + stringComparison.toString());
        }
    }

    /**
     * Returns true if a value equals with another. Uses a culture-aware higher performance string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static boolean equal(@NotNull final String a, @NotNull final String b, Locale locale, Collator collator,
            boolean caseSensitive) {
        return equalLocaleSensitive(a, b, locale, collator, caseSensitive);
    }

    /**
     * Locale-aware string equality comparison.
     */
    @Validate
    private static boolean equalLocaleSensitive(String a, String b, @NotNull final Locale locale,
            @NotNull final Collator collator, boolean caseSensitive) {
        if (!caseSensitive) {
            a = a.toLowerCase(locale);
            b = b.toLowerCase(locale);
        }

        return collator.equals(a, b);
    }

    /**
     * Compares two strings for equality
     */
    private static boolean equalOrdinal(String a, String b, boolean caseSensitive) {
        if (a.length() != b.length())
            return false;

        return indexOfOrdinal(a, b, 0, 1, caseSensitive) == 0;
    }

    /**
     * Finds the first index encountered of a particular character. Returns -1 if not found.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static int indexOf(@NotNull final char[] array, char ch) {
        for (int i = 0; i < array.length; i++)
            if (array[i] == ch)
                return i;

        return -1;
    }

    /**
     * Returns the first index where a part is encountered within a string value. If the part is not existent, -1 is returned. Uses the
     * CurrentLocale string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    public static int indexOf(String value, String part) {
        return indexOf(value, part, 0, value.length(), StringComparison.CurrentLocale);
    }

    /**
     * Returns the first index where a part is encountered within a string value. If the part is not existent, -1 is returned. Uses the
     * specified string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    public static int indexOf(String value, String part, StringComparison stringComparison) {
        return indexOf(value, part, 0, value.length(), stringComparison);
    }

    /**
     * Returns the first index where a part is encountered within a string value. If the part is not existent, -1 is returned. Uses a
     * culture-aware higher performance string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static int indexOf(@NotNull final String value, @NotNull final String part, Locale locale,
            Collator collator, boolean caseSensitive) {
        return indexOfLocaleSensitive(value, part, 0, value.length(), locale, collator, caseSensitive);
    }

    /**
     * Returns the first, second, third, etc. index where a part is encountered within a string value, starting at the specified index and
     * moving to the right of the string. If the part is not existent, -1 is returned. Uses the specified string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static int indexOf(@NotNull final String value, @NotNull final String part, int startIndex, int count,
            StringComparison stringComparison) {
        switch (stringComparison) {
        case CurrentLocale:
            return indexOfLocaleSensitive(value, part, startIndex, count, CURRENT_LOCALE, CURRENT_LOCALE_COLLATOR,
                    true);
        case CurrentLocaleIgnoreCase:
            return indexOfLocaleSensitive(value, part, startIndex, count, CURRENT_LOCALE, CURRENT_LOCALE_COLLATOR,
                    false);
        case InvariantLocale:
            return indexOfLocaleSensitive(value, part, startIndex, count, INVARIANT_LOCALE,
                    INVARIANT_LOCALE_COLLATOR, true);
        case InvariantLocaleIgnoreCase:
            return indexOfLocaleSensitive(value, part, startIndex, count, INVARIANT_LOCALE,
                    INVARIANT_LOCALE_COLLATOR, false);
        case Ordinal:
            return indexOfOrdinal(value, part, startIndex, count, true);
        case OrdinalIgnoreCase:
            return indexOfOrdinal(value, part, startIndex, count, false);
        default:
            throw new IllegalArgumentException(
                    "stringComparison has an unexpected value: " + stringComparison.toString());
        }
    }

    /**
     * Returns the first, second, third, etc. index where a part is encountered within a string value, starting at the specified index and
     * moving to the right of the string. If the part is not existent, -1 is returned. Uses a culture-aware higher performance string
     * comparison.
     * 
     * @throws NullPointerException An argument is null.
     * @throws IndexOutOfBoundsException An index is out of bounds
     * @throws IllegalArgumentException An argument is out of range
     */
    @Validate
    public static int indexOf(@NotNull final String value, @NotNull final String part, int startIndex, int count,
            Locale locale, Collator collator, boolean caseSensitive) {
        return indexOfLocaleSensitive(value, part, startIndex, count, locale, collator, caseSensitive);
    }

    /**
     * Performs a locale-sensitive string comparison
     * 
     * @param value The source value
     * @param part The value we're looking for
     * @param startIndex The index in source where we start looking
     * @param count Up to how many positions to look forward
     * @param locale The locale to use for toLowercase conversions, if a case sensitive comparison
     * @param collator The collator to use to compare locale-sensitive strings
     * @param caseSensitive Whether case sensitive or not
     * 
     * @return The index where the part was found in value, or -1 if not found.
     * 
     * @throws NullPointerException An argument is null.
     * @throws IndexOutOfBoundsException An index is out of bounds
     * @throws IllegalArgumentException An argument is out of range
     */
    @Validate
    private static int indexOfLocaleSensitive(String value, String part, int startIndex, int count,
            @NotNull final Locale locale, @NotNull final Collator collator, boolean caseSensitive) {
        int valueLen = value.length();
        int partLen = part.length();
        int endIndex = startIndex + count;

        if (startIndex > valueLen)
            throw new IndexOutOfBoundsException("startIndex=" + startIndex + " valueLen=" + valueLen);

        if (valueLen == 0)
            if (partLen == 0)
                return 0;
            else
                return -1;

        if (startIndex < 0)
            throw new IndexOutOfBoundsException("startIndex=" + startIndex);
        if (count < 0 || endIndex > valueLen)
            throw new IllegalArgumentException(
                    "count=" + count + " endIndex=" + endIndex + " valueLen=" + valueLen);

        // select comparison type
        if (caseSensitive)
            // case-sensitive comparison from start position to end position
            for (int i = startIndex; i < endIndex; i++)
                // some Strings have different lengths but are equal
                // e.g. for US: "\u00C4" "LATIN CAPITAL LETTER A WITH DIAERESIS" () and "\u0041\u0308" "LATIN CAPITAL LETTER A" (A) -
                // "COMBINING DIAERESIS" ()
                // therefore we have to examine whether the part is equal to ANY portion of the length of the string
                for (int j = 1; j < valueLen - i + 1; j++) {
                    String val = value.substring(i, i + j);
                    if (collator.equals(val, part))
                        return i;
                }
        else {
            String lowerPart = part.toLowerCase(locale);

            // case-insensitive comparison from start position to end position
            for (int i = startIndex; i < endIndex; i++)
                // some Strings have different lengths but are equal such as the diaeresis example above,
                // additionally (for this case) in some instances the string lengths of the toLowercase conversion will differ
                // e.g. for LT: "\u00cc" () becomes 3 characters (i)
                for (int j = 1; j < valueLen - i + 1; j++) {
                    String val = value.substring(i, i + j);
                    if (collator.equals(val.toLowerCase(locale), lowerPart))
                        return i;
                }
        }

        return -1;
    }

    /**
     * Performs an ordinal string comparison.
     * 
     * @param value The source value
     * @param part The value we're looking for
     * @param startIndex The index in source where we start looking
     * @param count Up to how many positions to look forward
     * @param caseSensitive Whether case sensitive or not
     * 
     * @return The index where the part was found in value, or -1 if not found.
     * 
     * @throws NullPointerException An argument is null.
     * @throws IndexOutOfBoundsException An index is out of bounds
     * @throws IllegalArgumentException An argument is out of range
     */
    private static int indexOfOrdinal(String value, String part, int startIndex, int count, boolean caseSensitive) {
        int valueLen = value.length();
        int partLen = part.length();

        if (startIndex > valueLen)
            throw new IndexOutOfBoundsException("startIndex=" + startIndex);
        if (valueLen == 0)
            if (partLen == 0)
                return 0;
            else
                return -1;

        if (startIndex < 0)
            throw new IndexOutOfBoundsException("startIndex=" + startIndex);
        if ((count < 0) || startIndex + count > valueLen || startIndex + count < 0)
            throw new IllegalArgumentException(
                    "count=" + count + " startIndex=" + startIndex + " partLen=" + partLen);

        // calculate the least amount of time we have to iterate through string characters
        int minCount = valueLen - partLen + 1;
        if (count + startIndex < minCount)
            minCount = count + startIndex;

        char[] vArr = value.toCharArray();
        char[] pArr = part.toCharArray();

        if (caseSensitive)
            for (int i = startIndex; i < minCount; i++) {
                boolean found = true;
                for (int j = 0; j < partLen; j++) {
                    char vCh = vArr[i + j];
                    char pCh = pArr[j];
                    // compare chars
                    if (vCh != pCh) {
                        found = false;
                        break;
                    }
                }
                if (found)
                    return i;
            }
        else
            // case insensitive
            for (int i = startIndex; i < minCount; i++) {
                boolean found = true;
                for (int j = 0; j < partLen; j++) {
                    char vCh = vArr[i + j];
                    char pCh = pArr[j];

                    // ASCII letters are converted to lowercase
                    if ((vCh >= 65 && vCh <= 90) || (vCh >= 97 && vCh <= 122)) {
                        vCh = Character.toLowerCase(vCh);
                        pCh = Character.toLowerCase(pCh);
                    }
                    // compare chars
                    if (vCh != pCh) {
                        found = false;
                        break;
                    }
                }
                if (found)
                    return i;
            }

        return -1;
    }

    /**
     * Returns the index of the first/second/third/etc. occurrence of an element in a value, starting from the left and moving forward.
     * 
     * @throws NullPointerException An argument is null.
     * @throws IllegalArgumentException Occurrence is out of range.
     */
    @Validate
    public static int indexOf(@NotNull final String value, @NotNull final String part, int occurrence,
            StringComparison stringComparison) {
        if (value.length() <= 0 || part.length() <= 0)
            return 0;
        if (occurrence <= 0)
            throw new IllegalArgumentException("occurrence" + occurrence);

        int result = 0;
        int index = 0;

        while ((index = indexOf(value, part, index, value.length() - index, stringComparison)) >= 0) {
            result++;

            // check if occurrence reached, if so we found our result
            if (result == occurrence)
                return index;

            index++;
        }

        return -1;
    }

    /**
     * Returns the index of the first/second/third/etc. occurrence of an element in a value, starting from the left and moving forward. Uses a
     * culture-aware higher performance string comparison.
     * 
     * @throws NullPointerException An argument is null.
     * @throws IllegalArgumentException Occurrence is out of range.
     */
    @Validate
    public static int indexOf(@NotNull final String value, @NotNull final String part, int occurrence,
            Locale locale, Collator collator, boolean caseSensitive) {
        if (value.length() <= 0 || part.length() <= 0)
            return 0;
        if (occurrence <= 0)
            throw new IllegalArgumentException("occurrence=" + occurrence);

        int result = 0;
        int index = 0;

        while ((index = indexOf(value, part, index, value.length() - index, locale, collator,
                caseSensitive)) >= 0) {
            result++;

            // check if occurrence reached, if so we found our result
            if (result == occurrence)
                return index;

            index++;
        }

        return -1;
    }

    /**
     * Inserts a string in a position in the given string.
     * 
     * @throws NullPointerException An argument is null.
     * @throws IndexOutOfBoundsException Index is out of range.
     */
    @Validate
    public static String insert(@NotNull final String value, int index, @NotNull final String insertedValue) {
        if (index < 0 || index > value.length())
            throw new IndexOutOfBoundsException("index=" + index + " length=" + value.length());

        return value.substring(0, index) + insertedValue + value.substring(index);
    }

    /**
     * Returns true if the given string is null or empty.
     */
    public static boolean isNullOrEmpty(String value) {
        return value == null || value.length() == 0;
    }

    /**
     * Returns true if the given string is null or contains only whitespace chars (' ', '\t', '\r' and '\n').
     * 
     * @author Marcin Lamparski
     */
    public static boolean isNullOrBlank(String value) {
        return value == null || trim(value).length() == 0;
    }

    /**
     * Finds the last index encountered of a particular character. Returns -1 if not found.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static int lastIndexOf(@NotNull final char[] array, char ch) {
        for (int i = array.length - 1; i >= 0; i--)
            if (array[i] == ch)
                return i;

        return -1;
    }

    /**
     * Returns the last index where a part is encountered within a string value. If the part is not existent, -1 is returned. Uses the
     * CurrentLocale string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    public static int lastIndexOf(String value, String part) {
        return lastIndexOf(value, part, StringComparison.CurrentLocale);
    }

    /**
     * Returns the last index where a part is encountered within a string value. If the part is not existent, -1 is returned. Uses the
     * specified string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    public static int lastIndexOf(String value, String part, StringComparison stringComparison) {
        return lastIndexOf(value, part, value.length() - 1, value.length(), stringComparison);
    }

    /**
     * Returns the last index where a part is encountered within a string value. If the part is not existent, -1 is returned. Uses a
     * culture-aware higher performance string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static int lastIndexOf(@NotNull final String value, @NotNull final String part, Locale locale,
            Collator collator, boolean caseSensitive) {
        return lastIndexOfLocaleSensitive(value, part, value.length() - 1, value.length(), locale, collator,
                caseSensitive);
    }

    /**
     * Returns the first, second, third, etc. index where a part is encountered within a string value, starting at the specified index and
     * moving to the left of the string. If the part is not existent, -1 is returned. Uses the specified string comparison.
     * 
     * @throws NullPointerException An argument is null.
     * @throws IndexOutOfBoundsException An index is out of bounds
     * @throws IllegalArgumentException An argument is out of range
     */
    @Validate
    public static int lastIndexOf(@NotNull final String value, @NotNull final String part, int startIndex,
            int count, StringComparison stringComparison) {
        switch (stringComparison) {
        case CurrentLocale:
            return lastIndexOfLocaleSensitive(value, part, startIndex, count, CURRENT_LOCALE,
                    CURRENT_LOCALE_COLLATOR, true);
        case CurrentLocaleIgnoreCase:
            return lastIndexOfLocaleSensitive(value, part, startIndex, count, CURRENT_LOCALE,
                    CURRENT_LOCALE_COLLATOR, false);
        case InvariantLocale:
            return lastIndexOfLocaleSensitive(value, part, startIndex, count, INVARIANT_LOCALE,
                    INVARIANT_LOCALE_COLLATOR, true);
        case InvariantLocaleIgnoreCase:
            return lastIndexOfLocaleSensitive(value, part, startIndex, count, INVARIANT_LOCALE,
                    INVARIANT_LOCALE_COLLATOR, false);
        case Ordinal:
            return lastIndexOfOrdinal(value, part, startIndex, count, true);
        case OrdinalIgnoreCase:
            return lastIndexOfOrdinal(value, part, startIndex, count, false);
        default:
            throw new IllegalArgumentException(
                    "stringComparison has an unexpected value: " + stringComparison.toString());
        }
    }

    /**
     * Returns the first, second, third, etc. index where a part is encountered within a string value, starting at the specified index and
     * moving to the left of the string. If the part is not existent, -1 is returned. Uses a culture-aware higher performance string
     * comparison.
     * 
     * @throws NullPointerException An argument is null.
     * @throws IndexOutOfBoundsException An index is out of bounds
     * @throws IllegalArgumentException An argument is out of range
     */
    @Validate
    public static int lastIndexOf(@NotNull final String value, @NotNull final String part, int startIndex,
            int count, Locale locale, Collator collator, boolean caseSensitive) {
        return lastIndexOfLocaleSensitive(value, part, startIndex, count, locale, collator, caseSensitive);
    }

    /**
     * Performs a locale-sensitive string comparison
     * 
     * @param value The source value
     * @param part The value we're looking for
     * @param startIndex The index in source where we start looking
     * @param count Up to how many positions to look backward
     * @param locale The locale to use for toLowercase conversions, if a case sensitive comparison
     * @param collator The collator to use to compare locale-sensitive strings
     * @param caseSensitive Whether case sensitive or not
     * 
     * @return The index where the part was found in value, or -1 if not found.
     * 
     * @throws NullPointerException An argument is null.
     * @throws IndexOutOfBoundsException An index is out of bounds
     * @throws IllegalArgumentException An argument is out of range
     */
    @Validate
    private static int lastIndexOfLocaleSensitive(String value, String part, int startIndex, int count,
            @NotNull final Locale locale, @NotNull final Collator collator, boolean caseSensitive) {
        int valueLen = value.length();
        int partLen = part.length();
        int endIndex = startIndex - count + 1;

        if ((valueLen == 0) && ((startIndex == -1) || (startIndex == 0)))
            if (partLen != 0)
                return -1;
            else
                return 0;

        if ((startIndex < 0) || (startIndex > valueLen))
            throw new IndexOutOfBoundsException("startIndex=" + startIndex + " valueLen=" + valueLen);

        if (count < 0 || endIndex < 0)
            throw new IllegalArgumentException("count=" + count + " startIndex=" + startIndex);

        // select comparison type
        if (caseSensitive)
            // case-sensitive comparison from start position to end position
            for (int i = startIndex + 1; i > endIndex; i--)
                // some Strings have different lengths but are equal
                // e.g. for US: "\u00C4" "LATIN CAPITAL LETTER A WITH DIAERESIS" () and "\u0041\u0308" "LATIN CAPITAL LETTER A" (A) -
                // "COMBINING DIAERESIS" ()
                // therefore we have to examine whether the part is equal to ANY portion of the length of the string
                for (int j = i - 1; j >= 0; j--) {
                    String val = value.substring(j, i);
                    if (collator.equals(val, part))
                        return j;
                }
        else {
            String lowerPart = part.toLowerCase(locale);

            // case-insensitive comparison from start position to end position
            for (int i = startIndex + 1; i > endIndex; i--)
                // some Strings have different lengths but are equal such as the diaeresis example above,
                // additionally (for this case) in some instances the string lengths of the toLowercase conversion will differ
                // e.g. for LT: "\u00cc" () becomes 3 characters (i)
                for (int j = i - 1; j >= 0; j--) {
                    String val = value.substring(j, i);
                    if (collator.equals(val.toLowerCase(locale), lowerPart))
                        return j;
                }
        }

        return -1;
    }

    /**
     * Performs an ordinal string comparison.
     * 
     * @param value The source value
     * @param part The value we're looking for
     * @param startIndex The index in source where we start looking
     * @param count Up to how many positions to look forward
     * @param caseSensitive Whether case sensitive or not
     * 
     * @return The index where the part was found in value, or -1 if not found.
     * 
     * @throws NullPointerException An argument is null.
     * @throws IndexOutOfBoundsException An index is out of bounds
     * @throws IllegalArgumentException An argument is out of range
     */
    private static int lastIndexOfOrdinal(String value, String part, int startIndex, int count,
            boolean caseSensitive) {
        int valueLen = value.length();
        int partLen = part.length();
        int endIndex = startIndex - count + 1;

        if ((valueLen == 0) && (startIndex == -1 || startIndex == 0))
            if (partLen != 0)
                return -1;
            else
                return 0;

        if ((startIndex < 0) || (startIndex > valueLen))
            throw new IndexOutOfBoundsException("startIndex=" + startIndex + " valueLen=" + valueLen);

        // following commented out: replaced by this
        if ((count < 0) || endIndex < 0)
            throw new IllegalArgumentException("count=" + count + " startIndex=" + startIndex);

        // calculate the least amount of time we have to iterate through string characters
        int minCount = startIndex - (valueLen - partLen);
        if (endIndex > minCount)
            minCount = endIndex;

        char[] vArr = value.toCharArray();
        char[] pArr = part.toCharArray();

        if (caseSensitive)
            for (int i = startIndex; i >= minCount; i--) {
                boolean found = true;
                for (int j = 0; j < partLen; j++)
                    if (i - j < 0)
                        found = false;
                    else {
                        char vCh = vArr[i - j];
                        char pCh = pArr[partLen - j - 1];
                        // compare chars
                        if (vCh != pCh) {
                            found = false;
                            break;
                        }
                    }

                if (found)
                    return i - partLen + 1;
            }
        else
            // case insensitive
            for (int i = startIndex; i >= minCount; i--) {
                boolean found = true;
                for (int j = 0; j < partLen; j++)
                    if (i - j < 0)
                        found = false;
                    else {
                        char vCh = vArr[i - j];
                        char pCh = pArr[partLen - j - 1];

                        // ASCII letters are converted to lowercase
                        if ((vCh >= 65 && vCh <= 90) || (vCh >= 97 && vCh <= 122)) {
                            vCh = Character.toLowerCase(vCh);
                            pCh = Character.toLowerCase(pCh);
                        }
                        // compare chars
                        if (vCh != pCh) {
                            found = false;
                            break;
                        }
                    }

                if (found)
                    return i - partLen + 1;
            }

        return -1;
    }

    /**
     * Returns the index of the first/second/third/etc. occurrence of an element in a value, starting from the right and moving backward.
     * 
     * @throws NullPointerException An argument is null.
     * @throws IllegalArgumentException Occurrence is out of range.
     */
    @Validate
    public static int lastIndexOf(@NotNull final String value, @NotNull final String part, int occurrenceFromEnd,
            StringComparison stringComparison) {
        if (value.length() <= 0 || part.length() <= 0)
            return 0;
        if (occurrenceFromEnd <= 0)
            throw new IllegalArgumentException("occurrenceFromEnd" + occurrenceFromEnd);

        int result = 0;
        int index = value.length() - 1;

        while ((index = lastIndexOf(value, part, index, index + 1, stringComparison)) >= 0) {
            result++;

            // check if occurrence reached, if so we found our result
            if (result == occurrenceFromEnd)
                return index;

            index--;
            if (index < 0)
                break;
        }

        return -1;
    }

    /**
     * Returns the index of the first/second/third/etc. occurrence of an element in a value, starting from the right and moving backward. Uses
     * a culture-aware higher performance string comparison.
     * 
     * @throws NullPointerException An argument is null.
     * @throws IllegalArgumentException Occurrence is out of range.
     */
    @Validate
    public static int lastIndexOf(@NotNull final String value, @NotNull final String part, int occurrenceFromEnd,
            Locale locale, Collator collator, boolean caseSensitive) {
        if (value.length() <= 0 || part.length() <= 0)
            return 0;
        if (occurrenceFromEnd <= 0)
            throw new IllegalArgumentException("occurrenceFromEnd" + occurrenceFromEnd);

        int result = 0;
        int index = value.length() - 1;

        while ((index = lastIndexOf(value, part, index, index + 1, locale, collator, caseSensitive)) >= 0) {
            result++;

            // check if occurrence reached, if so we found our result
            if (result == occurrenceFromEnd)
                return index;

            index--;
            if (index < 0)
                break;
        }

        return -1;
    }

    /**
     * Allows for matching a string to another, using Equals, StartsWith, EndsWith or Contains and a string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static boolean match(@NotNull final String a, MatchType stringMatch, @NotNull final String b,
            StringComparison stringComparison) {
        switch (stringMatch) {
        case Equals:
            return equal(a, b, stringComparison);
        case Contains:
            return indexOf(a, b, stringComparison) >= 0;
        case StartsWith:
            return startsWith(a, b, stringComparison);
        case EndsWith:
            return endsWith(a, b, stringComparison);
        default:
            throw new IllegalArgumentException("Unrecognized string match type: " + stringMatch);
        }
    }

    /**
     * Allows for matching a string to another, using Equals, StartsWith, EndsWith or Contains and a string comparison. Uses a culture-aware
     * higher performance string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static boolean match(@NotNull final String a, MatchType stringMatch, @NotNull final String b,
            Locale locale, Collator collator, boolean caseSensitive) {
        switch (stringMatch) {
        case Equals:
            return equal(a, b, locale, collator, caseSensitive);
        case Contains:
            return indexOf(a, b, locale, collator, caseSensitive) >= 0;
        case StartsWith:
            return startsWith(a, b, locale, collator, caseSensitive);
        case EndsWith:
            return endsWith(a, b, locale, collator, caseSensitive);
        default:
            throw new IllegalArgumentException("Unrecognized string match type: " + stringMatch);
        }
    }

    /**
     * Right-aligns the characters in this instance, padding on the left a specified character
     * 
     * @throws NullPointerException An argument is null.
     * @throws IllegalArgumentException An invalid argument was given.
     */
    @Validate
    public static String padRight(@NotNull final String value, int totalLength, char pad) {
        if (totalLength < 0)
            throw new IllegalArgumentException("totalLength=" + totalLength);

        int add = totalLength - value.length();
        if (add < 0)
            throw new IllegalArgumentException("totalLength=" + totalLength + " len=" + value.length());

        StringBuilder str = new StringBuilder(value);
        char[] ch = new char[add];
        Arrays.fill(ch, pad);
        str.append(ch);

        return str.toString();
    }

    /**
     * Left-aligns the characters in this instance, padding on the right a specified character
     * 
     * @throws NullPointerException An argument is null.
     * @throws IllegalArgumentException An invalid argument was given.
     */
    @Validate
    public static String padLeft(@NotNull final String value, int totalLength, char pad) {
        if (totalLength < 0)
            throw new IllegalArgumentException("totalLength=" + totalLength);

        int add = totalLength - value.length();
        if (add < 0)
            throw new IllegalArgumentException("totalLength=" + totalLength + " len=" + value.length());

        StringBuilder str = new StringBuilder(value);
        char[] ch = new char[add];
        Arrays.fill(ch, pad);
        str.insert(0, ch);

        return str.toString();
    }

    /**
     * Parses a boolean from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    public static boolean parseBool(String value) {
        return parseBool(value, CONSTANT.TRUE, CONSTANT.FALSE, StringComparison.OrdinalIgnoreCase);
    }

    /**
     * Parses a boolean from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    @Validate
    public static boolean parseBool(@NotNull final String value, @NotNull final String trueValue,
            @NotNull final String falseValue, StringComparison comparisonType) {
        // parse
        if (equal(trueValue, value, comparisonType))
            return true;
        else if (equal(falseValue, value, comparisonType))
            return false;

        // sanity check
        throw new NumberFormatException(
                "Value given was neither " + trueValue + " nor " + falseValue + ": " + value);
    }

    /**
     * Parses the first character from a string.
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    public static char parseChar(String value) {
        return parseChar(value, Character.MIN_VALUE, Character.MAX_VALUE);
    }

    /**
     * Parses the first character from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    @Validate
    public static char parseChar(@NotNull final String value, char minValue, char maxValue) {
        if (value.length() != 1)
            throw new NumberFormatException("Value is not 1 character long: " + value);

        char result = value.charAt(0);

        // sanity check
        if (result < minValue)
            throw new NumberFormatException(
                    "Value (" + result + ") was less than allowed minimum (" + minValue + ").");
        if (result > maxValue)
            throw new NumberFormatException(
                    "Value (" + result + ") was more than allowed maximum (" + maxValue + ").");

        return result;
    }

    /**
     * Parses a DateTime from a string. Uses common ISO formats as well as some locale-specific formats. See
     * http://joda-time.sourceforge.net/apidocs/org/joda/time/format/DateTimeFormat.html ISO format examples:
     * <ul>
     * <li>yyyyMMdd'T'HHmmssZ</li>
     * <li>yyyyMMdd'T'HHmmss.SSSZ</li>
     * <li>yyyy-MM-dd</li>
     * <li>yyyy-MM-dd'T'HH:mm:ss.SSS</li>
     * <li>yyyy-MM-dd'T'HH:mm:ssZZ</li>
     * <li>yyyy-MM-dd'T'HH:mm:ss.SSSZZ</li>
     * <li>yyyy-MM-dd HH:mm:ss</li>
     * <li>yyyy-MM-dd HH:mm:ss.SSSSSSS</li>
     * <li>yyyy-MM-dd'T'HH:mm:ss</li>
     * <li>yyyy-MM-dd'T'HH:mm:ss.SSSSSSS</li>
     * </ul>
     * <p/>
     * Also supports non-ISO formats such as yyyy/MM/dd. Furthermore attempts to parse using locale-specific parsers.
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    @Validate
    public static LocalDateTime parseDateTime(@NotNull final String value) {
        LocalDateTime result = null;

        // attempt ISO standard parsing
        try {
            result = STANDARD_FORMATTERS.parseDateTime(value).toLocalDateTime();
        } catch (Throwable e) {
            // continues parsing attempts
        }

        if (result == null)
            // first try locale-specific date/time parsing
            for (int dateStyle = DateFormat.FULL; dateStyle <= DateFormat.SHORT; dateStyle++)
                for (int timeStyle = DateFormat.FULL; timeStyle <= DateFormat.SHORT; timeStyle++)
                    if (result == null)
                        try {
                            // Parse with a default format
                            Date date = DateFormat.getDateTimeInstance(dateStyle, timeStyle, CURRENT_LOCALE)
                                    .parse(value);
                            result = new LocalDateTime(date);

                            break;
                        } catch (ParseException e) {
                            continue;
                        }

        if (result == null)
            // now try locale-specific date parsing
            for (int dateStyle = DateFormat.FULL; dateStyle <= DateFormat.SHORT; dateStyle++)
                try {
                    // Parse with a default format
                    Date date = DateFormat.getDateInstance(dateStyle, CURRENT_LOCALE).parse(value);
                    result = new LocalDateTime(date);
                    break;
                } catch (ParseException e) {
                    continue;
                }

        if (result == null)
            // lastly try locale-specific time parsing
            for (int timeStyle = DateFormat.FULL; timeStyle <= DateFormat.SHORT; timeStyle++)
                try {
                    // Parse with a default format
                    Date date = DateFormat.getTimeInstance(timeStyle, CURRENT_LOCALE).parse(value);
                    result = new LocalDateTime(date);
                    break;
                } catch (ParseException e) {
                    continue;
                }

        if (result == null)
            throw new NumberFormatException("The specified date/time is not in an identifiable format: " + value);

        // sanity check
        if (result.compareTo(MIN_DATETIME) < 0)
            throw new NumberFormatException(
                    "Value (" + result + ") was less than allowed minimum (" + MIN_DATETIME + ").");
        if (result.compareTo(MAX_DATETIME) > 0)
            throw new NumberFormatException(
                    "Value (" + result + ") was more than allowed maximum (" + MAX_DATETIME + ").");

        return result;
    }

    /**
     * Parses a DateTime from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    public static LocalDateTime parseDateTime(String value, DateTimeFormatter formatter) {
        return parseDateTime(value, MIN_DATETIME, MAX_DATETIME, formatter);
    }

    /**
     * Parses a DateTime from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    @Validate
    public static LocalDateTime parseDateTime(@NotNull final String value, @NotNull final LocalDateTime minValue,
            @NotNull final LocalDateTime maxValue, @NotNull final DateTimeFormatter formatter) {
        // parse
        LocalDateTime result = formatter.parseDateTime(value).toLocalDateTime();

        // sanity check
        if (result.compareTo(minValue) < 0)
            throw new NumberFormatException(
                    "Value (" + result + ") was less than allowed minimum (" + minValue + ").");
        if (result.compareTo(maxValue) > 0)
            throw new NumberFormatException(
                    "Value (" + result + ") was more than allowed maximum (" + maxValue + ").");

        return result;
    }

    /**
     * Parses a decimal from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    @Validate
    public static BigDecimal parseDecimal(@NotNull final String value) {
        // parse
        BigDecimal result;
        try {
            // check if the current locale uses the same decimal separator and grouping chars as expected by the parser
            if (DECIMAL_SEPARATOR != CONSTANT.COMMA_CHAR) {
                // remove grouping separators and replace decimal separators to commas
                String converted = StringUtils.replace(value, GROUPING_SEPARATOR + CONSTANT.EMPTY_STRING,
                        CONSTANT.EMPTY_STRING, StringComparison.Ordinal);
                result = new BigDecimal(converted);
            } else
                result = new BigDecimal(value);
        } catch (Throwable e) {
            throw new NumberFormatException("The value '" + value + "' could not be parsed: " + e.getMessage());
        }

        return result;
    }

    /**
     * Parses a decimal from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    @Validate
    public static BigDecimal parseDecimal(@NotNull final String value, @NotNull final BigDecimal minValue,
            @NotNull final BigDecimal maxValue) {
        // parse
        BigDecimal result;
        try {
            // check if the current locale uses the same decimal separator and grouping chars as expected by the parser
            if (DECIMAL_SEPARATOR != CONSTANT.COMMA_CHAR) {
                // remove grouping separators and replace decimal separators to commas
                String converted = StringUtils.replace(value, GROUPING_SEPARATOR + CONSTANT.EMPTY_STRING,
                        CONSTANT.EMPTY_STRING, StringComparison.Ordinal);
                result = new BigDecimal(converted);
            } else
                result = new BigDecimal(value);
        } catch (Throwable e) {
            throw new NumberFormatException("The value '" + value + "' could not be parsed: " + e.getMessage());
        }

        // sanity check
        if (result.compareTo(minValue) < 0)
            throw new NumberFormatException(
                    "Value (" + result + ") was less than allowed minimum (" + minValue + ").");
        if (result.compareTo(maxValue) > 0)
            throw new NumberFormatException(
                    "Value (" + result + ") was more than allowed maximum (" + maxValue + ").");

        return result;
    }

    /**
     * Parses a double from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    public static double parseDouble(String value) {
        return parseDouble(value, -Double.MAX_VALUE, Double.MAX_VALUE, true, true);
    }

    /**
     * Parses a double from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    @Validate
    public static double parseDouble(@NotNull final String value, double minValue, double maxValue,
            boolean allowInfinity, boolean allowNaN) {
        // parse
        double result;
        try {
            // check if the current locale uses the same decimal separator and grouping chars as expected by the parser
            if (DECIMAL_SEPARATOR != CONSTANT.COMMA_CHAR) {
                // remove grouping separators and replace decimal separators to commas
                String converted = StringUtils.replace(value, GROUPING_SEPARATOR + CONSTANT.EMPTY_STRING,
                        CONSTANT.EMPTY_STRING, StringComparison.Ordinal);
                result = Double.parseDouble(converted);
            } else
                result = Double.parseDouble(value);
        } catch (Throwable e) {
            throw new NumberFormatException("The value '" + value + "' could not be parsed: " + e.getMessage());
        }

        if (allowInfinity)
            if (result == Double.POSITIVE_INFINITY || result == Double.NEGATIVE_INFINITY)
                return result;
        if (allowNaN)
            if (result == Double.NaN)
                return result;

        // sanity check
        if (result < minValue)
            throw new NumberFormatException(
                    "Value (" + result + ") was less than allowed minimum (" + minValue + ").");
        if (result > maxValue)
            throw new NumberFormatException(
                    "Value (" + result + ") was more than allowed maximum (" + maxValue + ").");

        return result;
    }

    /**
     * Parses a float from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    public static float parseFloat(String value) {
        return parseFloat(value, -Float.MAX_VALUE, Float.MAX_VALUE, true, true);
    }

    /**
     * Parses a float from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    @Validate
    public static float parseFloat(@NotNull final String value, float minValue, float maxValue,
            boolean allowInfinity, boolean allowNaN) {
        // parse
        float result;
        try {
            // check if the current locale uses the same decimal separator and grouping chars as expected by the parser
            if (DECIMAL_SEPARATOR != CONSTANT.COMMA_CHAR) {
                // remove grouping separators and replace decimal separators to commas
                String converted = StringUtils.replace(value, GROUPING_SEPARATOR + CONSTANT.EMPTY_STRING,
                        CONSTANT.EMPTY_STRING, StringComparison.Ordinal);
                result = Float.parseFloat(converted);
            } else
                result = Float.parseFloat(value);
        } catch (Throwable e) {
            throw new NumberFormatException("The value '" + value + "' could not be parsed: " + e.getMessage());
        }

        if (allowInfinity)
            if (result == Double.POSITIVE_INFINITY || result == Double.NEGATIVE_INFINITY)
                return result;
        if (allowNaN)
            if (result == Double.NaN)
                return result;

        // sanity check
        if (result < minValue)
            throw new NumberFormatException(
                    "Value (" + result + ") was less than allowed minimum (" + minValue + ").");
        if (result > maxValue)
            throw new NumberFormatException(
                    "Value (" + result + ") was more than allowed maximum (" + maxValue + ").");

        return result;
    }

    /**
     * Parses a byte from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    public static byte parseInt8(String value) {
        return parseInt8(value, Byte.MIN_VALUE, Byte.MAX_VALUE);
    }

    /**
     * Parses a byte from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    @Validate
    public static byte parseInt8(@NotNull final String value, byte minValue, byte maxValue) {
        // parse
        byte result;
        try {
            result = Byte.parseByte(value);
        } catch (Throwable e) {
            throw new NumberFormatException("The value '" + value + "' could not be parsed: " + e.getMessage());
        }

        // sanity check
        if (result < minValue)
            throw new NumberFormatException(
                    "Value (" + result + ") was less than allowed minimum (" + minValue + ").");
        if (result > maxValue)
            throw new NumberFormatException(
                    "Value (" + result + ") was more than allowed maximum (" + maxValue + ").");

        return result;
    }

    /**
     * Parses a short from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    public static short parseInt16(String value) {
        return parseInt16(value, Short.MIN_VALUE, Short.MAX_VALUE);
    }

    /**
     * Parses a short from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    @Validate
    public static short parseInt16(@NotNull final String value, short minValue, short maxValue) {
        // parse
        short result;
        try {
            result = Short.parseShort(value);
        } catch (Throwable e) {
            throw new NumberFormatException("The value '" + value + "' could not be parsed: " + e.getMessage());
        }

        // sanity check
        if (result < minValue)
            throw new NumberFormatException(
                    "Value (" + result + ") was less than allowed minimum (" + minValue + ").");
        if (result > maxValue)
            throw new NumberFormatException(
                    "Value (" + result + ") was more than allowed maximum (" + maxValue + ").");

        return result;
    }

    /**
     * Parses an int from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    public static int parseInt32(String value) {
        return parseInt32(value, Integer.MIN_VALUE, Integer.MAX_VALUE);
    }

    /**
     * Parses an int from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    @Validate
    public static int parseInt32(@NotNull final String value, int minValue, int maxValue) {
        // parse
        int result;
        try {
            result = Integer.parseInt(value);
        } catch (Throwable e) {
            throw new NumberFormatException("The value '" + value + "' could not be parsed: " + e.getMessage());
        }

        // sanity check
        if (result < minValue)
            throw new NumberFormatException(
                    "Value (" + result + ") was less than allowed minimum (" + minValue + ").");
        if (result > maxValue)
            throw new NumberFormatException(
                    "Value (" + result + ") was more than allowed maximum (" + maxValue + ").");

        return result;
    }

    /**
     * Parses a long from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    public static long parseInt64(String value) {
        return parseInt64(value, Long.MIN_VALUE, Long.MAX_VALUE);
    }

    /**
     * Parses a long from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    @Validate
    public static long parseInt64(@NotNull final String value, long minValue, long maxValue) {
        // parse
        long result;
        try {
            result = Long.parseLong(value);
        } catch (Throwable e) {
            throw new NumberFormatException("The value '" + value + "' could not be parsed: " + e.getMessage());
        }

        // sanity check
        if (result < minValue)
            throw new NumberFormatException(
                    "Value (" + result + ") was less than allowed minimum (" + minValue + ").");
        if (result > maxValue)
            throw new NumberFormatException(
                    "Value (" + result + ") was more than allowed maximum (" + maxValue + ").");

        return result;
    }

    /**
     * Parses an Int128 from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    public static Int128 parseInt128(String value) {
        return parseInt128(value, Int128.MIN_VALUE, Int128.MAX_VALUE);
    }

    /**
     * Parses an Int128 from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    @Validate
    public static Int128 parseInt128(@NotNull final String value, @NotNull final Int128 minValue,
            @NotNull final Int128 maxValue) {
        // parse
        Int128 result;
        try {
            result = new Int128(value);
        } catch (Throwable e) {
            throw new NumberFormatException("The value '" + value + "' could not be parsed: " + e.getMessage());
        }

        // sanity check
        if (result.compareTo(minValue) < 0)
            throw new NumberFormatException(
                    "Value (" + result + ") was less than allowed minimum (" + minValue + ").");
        if (result.compareTo(maxValue) > 0)
            throw new NumberFormatException(
                    "Value (" + result + ") was more than allowed maximum (" + maxValue + ").");

        return result;
    }

    /**
     * Parses the value of the given string: simply returns the string after checking for null.
     * 
     * @throws NullPointerException When an argument is null
     */
    @Validate
    public static String parseString(@NotNull final String value) {
        return value;
    }

    /**
     * Parses the value of the given object: simply returns the toString() result after checking for null, if the object is null the result is
     * null.
     */
    public static String parseString(Object value) {
        return value != null ? value.toString() : null;
    }

    /**
     * Parses a TimeSpan from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    public static Duration parseTimeSpan(String value) {
        return parseTimeSpan(value, MIN_DURATION, MAX_DURATION);
    }

    /**
     * Parses a TimeSpan from a string. TimeSpans are .NET structs and do not have an equivalent in Java. They express time durations in
     * ticks, which are units 1000 times smaller than milliseconds.
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    @Validate
    public static Duration parseTimeSpan(@NotNull final String value, @NotNull final Duration minValue,
            @NotNull final Duration maxValue) {
        Duration result;
        try {
            result = new Duration(Long.parseLong(value) / 1000);
        } catch (Throwable e) {
            throw new NumberFormatException("The value '" + value + "' could not be parsed: " + e.getMessage());
        }

        // sanity check
        if (result.compareTo(minValue) < 0)
            throw new NumberFormatException(
                    "Value (" + result + ") was less than allowed minimum (" + minValue + ").");
        if (result.compareTo(maxValue) > 0)
            throw new NumberFormatException(
                    "Value (" + result + ") was more than allowed maximum (" + maxValue + ").");

        return result;
    }

    /**
     * Parses an sbyte from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    public static UnsignedByte parseUInt8(String value) {
        return parseUInt8(value, UnsignedByte.MIN_VALUE, UnsignedByte.MAX_VALUE);
    }

    /**
     * Parses an sbyte from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    @Validate
    public static UnsignedByte parseUInt8(@NotNull final String value, @NotNull final UnsignedByte minValue,
            @NotNull final UnsignedByte maxValue) {
        // parse
        UnsignedByte result;
        try {
            result = new UnsignedByte(value);
        } catch (Throwable e) {
            throw new NumberFormatException("The value '" + value + "' could not be parsed: " + e.getMessage());
        }

        // sanity check
        if (result.compareTo(minValue) < 0)
            throw new NumberFormatException(
                    "Value (" + result + ") was less than allowed minimum (" + minValue + ").");
        if (result.compareTo(maxValue) > 0)
            throw new NumberFormatException(
                    "Value (" + result + ") was more than allowed maximum (" + maxValue + ").");

        return result;
    }

    /**
     * Parses a ushort from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    public static UnsignedShort parseUInt16(String value) {
        return parseUInt16(value, UnsignedShort.MIN_VALUE, UnsignedShort.MAX_VALUE);
    }

    /**
     * Parses a ushort from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    @Validate
    public static UnsignedShort parseUInt16(@NotNull final String value, @NotNull final UnsignedShort minValue,
            @NotNull final UnsignedShort maxValue) {
        // parse
        UnsignedShort result;
        try {
            result = new UnsignedShort(value);
        } catch (Throwable e) {
            throw new NumberFormatException("The value '" + value + "' could not be parsed: " + e.getMessage());
        }

        // sanity check
        if (result.compareTo(minValue) < 0)
            throw new NumberFormatException(
                    "Value (" + result + ") was less than allowed minimum (" + minValue + ").");
        if (result.compareTo(maxValue) > 0)
            throw new NumberFormatException(
                    "Value (" + result + ") was more than allowed maximum (" + maxValue + ").");

        return result;
    }

    /**
     * Parses a uint from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    public static UnsignedInteger parseUInt32(String value) {
        return parseUInt32(value, UnsignedInteger.MIN_VALUE, UnsignedInteger.MIN_VALUE);
    }

    /**
     * Parses a uint from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    @Validate
    public static UnsignedInteger parseUInt32(@NotNull final String value, @NotNull final UnsignedInteger minValue,
            @NotNull final UnsignedInteger maxValue) {
        // parse
        UnsignedInteger result;
        try {
            result = new UnsignedInteger(value);
        } catch (Throwable e) {
            throw new NumberFormatException("The value '" + value + "' could not be parsed: " + e.getMessage());
        }

        // sanity check
        if (result.compareTo(minValue) < 0)
            throw new NumberFormatException(
                    "Value (" + result + ") was less than allowed minimum (" + minValue + ").");
        if (result.compareTo(maxValue) > 0)
            throw new NumberFormatException(
                    "Value (" + result + ") was more than allowed maximum (" + maxValue + ").");

        return result;
    }

    /**
     * Parses a ulong from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    public static UnsignedLong parseUInt64(String value) {
        return parseUInt64(value, UnsignedLong.MIN_VALUE, UnsignedLong.MAX_VALUE);
    }

    /**
     * Parses a ulong from a string
     * 
     * @throws NullPointerException An argument is null.
     * @throws NumberFormatException Parsed value is outside of configured range, or not of correct type.
     */
    @Validate
    public static UnsignedLong parseUInt64(@NotNull final String value, @NotNull final UnsignedLong minValue,
            @NotNull final UnsignedLong maxValue) {
        // parse
        UnsignedLong result;
        try {
            result = new UnsignedLong(value);
        } catch (Throwable e) {
            throw new NumberFormatException("The value '" + value + "' could not be parsed: " + e.getMessage());
        }

        // sanity check
        if (result.compareTo(minValue) < 0)
            throw new NumberFormatException(
                    "Value (" + result + ") was less than allowed minimum (" + minValue + ").");
        if (result.compareTo(maxValue) > 0)
            throw new NumberFormatException(
                    "Value (" + result + ") was more than allowed maximum (" + maxValue + ").");

        return result;
    }

    /**
     * Replaces text in a value with the specified replacement text, using the given string comparison type.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static String replace(@NotNull String value, @NotNull final String textToReplace,
            @NotNull final String replaceWithText, StringComparison stringComparison) {
        if (textToReplace.length() == 0)
            return value;

        int index = 0;

        switch (stringComparison) {
        case CurrentLocale:
        case CurrentLocaleIgnoreCase:
        case InvariantLocale:
        case InvariantLocaleIgnoreCase:
            // for these we must not assume that the length is the same as textToReplace.length()
            while ((index = indexOf(value, textToReplace, index, value.length() - index, stringComparison)) >= 0) {
                String prefix = value.substring(0, index);
                for (int i = index + 1; i <= value.length(); i++) {
                    // find index
                    String replace = value.substring(index, i);
                    if (equal(replace, textToReplace, stringComparison)) {
                        // end index found, replace
                        value = prefix + replaceWithText + value.substring(i);
                        break;
                    }
                }
                index += replaceWithText.length();
            }
            break;
        case Ordinal:
        case OrdinalIgnoreCase:
            while ((index = indexOf(value, textToReplace, index, value.length() - index, stringComparison)) >= 0) {
                value = value.substring(0, index) + replaceWithText
                        + value.substring(index + textToReplace.length());
                index += replaceWithText.length();
            }
            break;
        default:
            throw new IllegalArgumentException("Unrecognized string comparison type: " + stringComparison);
        }

        return value;
    }

    /**
     * Replaces text in a value with the specified replacement text, using the given string comparison type. Uses a culture-aware higher
     * performance string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static String replace(@NotNull String value, @NotNull final String textToReplace,
            @NotNull final String replaceWithText, Locale locale, Collator collator, boolean caseSensitive) {
        if (textToReplace.length() == 0)
            return value;

        int index = 0;

        // for these we must not assume that the length is the same as textToReplace.length()
        while ((index = indexOf(value, textToReplace, index, value.length() - index, locale, collator,
                caseSensitive)) >= 0) {
            String prefix = value.substring(0, index);
            for (int i = index + 1; i <= value.length(); i++) {
                // find index
                String replace = value.substring(index, i);
                if (equal(replace, textToReplace, locale, collator, caseSensitive)) {
                    // end index found, replace
                    value = prefix + replaceWithText + value.substring(i);
                    break;
                }
            }
            index += replaceWithText.length();
        }

        return value;
    }

    /**
     * Replace method working on a string builder, for more efficient replacement.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static void replace(@NotNull final StringBuilder builder, @NotNull final String textToReplace,
            @NotNull final String replaceWithText) {
        int indexOfTarget = -1;
        while ((indexOfTarget = builder.indexOf(textToReplace)) > 0)
            builder.replace(indexOfTarget, indexOfTarget + textToReplace.length(), replaceWithText);
    }

    /**
     * Returns a number of ToString() result concatenations of the given character.
     * 
     * @throws NullPointerException An argument is null.
     * @throws IllegalArgumentException Repetitions argument is out of range.
     */
    public static String repeat(char value, int repetitions) {
        return repeat(Character.toString(value), repetitions);
    }

    /**
     * Returns a number of ToString() result concatenations of the given string value.
     * 
     * @throws NullPointerException An argument is null.
     * @throws IllegalArgumentException Repetitions argument is out of range.
     */
    @Validate
    public static String repeat(@NotNull final String value, int repetitions) {
        if (repetitions < 0)
            throw new IllegalArgumentException("repetitions=" + repetitions);

        StringBuilder sb = new StringBuilder(value.length() * repetitions);
        for (int i = 0; i < repetitions; i++)
            sb.append(value);

        return sb.toString();
    }

    /**
     * Reverses a string.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static String reverse(@NotNull final String value) {
        val sb = new StringBuilder(value);
        return sb.reverse().toString();
    }

    /**
     * Returns true if two character sequences are equal
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static boolean sequenceEqual(@NotNull final char[] a, @NotNull final char[] b) {
        if (a.length != b.length)
            return false;
        if (a.length == 0)
            return false;

        return sequenceEqual(a, 0, b, 0, a.length);
    }

    /**
     * Returns true if two character sequences are equal
     * 
     * @throws NullPointerException An argument is null.
     * @throws IndexOutOfBoundsException An index is out of bounds.
     * @throws IllegalArgumentException An argument is out of range.
     */
    @Validate
    public static boolean sequenceEqual(@NotNull final char[] a, int startIndexA, @NotNull final char[] b,
            int startIndexB, int count) {
        if (count == 0)
            return true;
        if (startIndexA < 0 || startIndexA > a.length)
            throw new IndexOutOfBoundsException("startIndexA=" + startIndexA + " aLen=" + a.length);
        if (startIndexB < 0 || startIndexB > b.length)
            throw new IndexOutOfBoundsException("startIndexB=" + startIndexB + " bLen=" + b.length);
        if (count < 0)
            throw new IllegalArgumentException("count=" + count);

        if (startIndexA + count > a.length || startIndexA + count < 0)
            throw new IllegalArgumentException(
                    "startIndexA=" + startIndexA + " count=" + count + " aLen=" + a.length);
        if (startIndexB + count > b.length || startIndexB + count < 0)
            throw new IllegalArgumentException(
                    "startIndexB=" + startIndexB + " count=" + count + " bLen=" + b.length);

        for (int i = 0; i < count; i++)
            if (a[startIndexA + i] != b[startIndexB + i])
                return false;

        return true;
    }

    /**
     * Splits a sequence into parts delimited by the specified delimited. Empty entries between delimiters are removed.
     * 
     * @throws NullPointerException When an argument is null, or an item in the iterable is null.
     */
    @Validate
    public static List<char[]> split(@NotNull final char[] values, char delimiter) {
        List<ReifiedList<Character>> parts = new ArrayList<ReifiedList<Character>>();
        parts.add(new ReifiedArrayList<Character>(Character.class));

        for (char item : values)
            if (item != delimiter)
                parts.get(parts.size() - 1).add(item);
            else
                parts.add(new ReifiedArrayList<Character>(Character.class));

        List<char[]> result = new ArrayList<char[]>();

        for (ReifiedList<Character> arr : parts)
            if (arr.size() > 0)
                result.add(ArrayUtils.unbox(arr.toArray()));

        return result;
    }

    /**
     * Splits a string, using StringSplitOptions.RemoveEmptyEntries.
     * 
     * @throws NullPointerException An argument is null.
     */
    public static String[] split(String text, char delimiter) {
        return split(text, delimiter, StringSplitOptions.RemoveEmptyEntries);
    }

    /**
     * Splits a string using the specified split option.
     * 
     * @throws NullPointerException An argument is null.
     */
    public static String[] split(String text, char delimiter, StringSplitOptions options) {
        char[] delimiters = new char[] { delimiter };
        return split(text, delimiters, options);
    }

    /**
     * Returns a string array that contains the substrings of the text instance that are delimited by elements of a specified Unicode
     * character array.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static String[] split(@NotNull final String text, @NotNull final char[] delimiters,
            StringSplitOptions options) {
        val result = new ReifiedArrayList<String>(String.class);

        // if no separators, return the original string
        if (delimiters.length == 0) {
            result.add(text);
            return result.toArray();
        }

        int lastFound = 0;
        int index = 0;

        for (char ch : text.toCharArray()) {
            // check if character is a separator
            if (contains(delimiters, ch)) {
                if (lastFound == index)
                    result.add(CONSTANT.EMPTY_STRING);
                else
                    // add part without separator
                    result.add(text.substring(lastFound, index));

                // mark last found position
                lastFound = index + 1;
            }
            index++;
        }
        // add last part if any
        if (index > lastFound)
            result.add(text.substring(lastFound, index));
        else if (index == lastFound && text.length() > 0 && contains(delimiters, text.charAt(text.length() - 1)))
            result.add(CONSTANT.EMPTY_STRING);

        switch (options) {
        case None:
            return result.toArray();
        case RemoveEmptyEntries:
            return Linq.where(result.toArray(), isNotNullOrEmpty());
        default:
            throw new IllegalArgumentException("stringSplitOptions has an unexpected value: " + options.toString());
        }
    }

    /**
     * Splits a string, using StringSplitOptions.RemoveEmptyEntries. When splitting strings, Ordinal string comparison is always used.
     * 
     * @throws NullPointerException An argument is null.
     */
    public static String[] split(String text, String delimiter) {
        return split(text, delimiter, StringSplitOptions.RemoveEmptyEntries);
    }

    /**
     * Splits a string using the specified split option. When splitting strings, Ordinal string comparison is always used.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static String[] split(@NotNull final String text, @NotNull final String delimiter,
            StringSplitOptions options) {
        val delimiters = new String[] { delimiter };
        return split(text, delimiters, options);
    }

    /**
     * Returns a string array that contains the substrings of the text instance that are delimited by elements provided in the specified
     * Unicode string array. When splitting strings, Ordinal string comparison is always used.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static String[] split(@NotNull final String text, @NotNull String[] delimiters,
            StringSplitOptions options) {
        // ignore null and empty delimiters
        delimiters = Linq.where(delimiters, isNotNullOrEmpty());

        // case where there are no delimiters
        ReifiedList<String> result = new ReifiedArrayList<String>(String.class);
        if (delimiters.length <= 0) {
            result.add(text);
            return result.toArray();
        }

        // simplify if all delimiters are chars, call character delimiter method
        if (Linq.all(delimiters, lengthEquals(1)))
            return split(text, ArrayUtils.unbox(Linq.select(delimiters, charAt(0))), options);

        // multiple delimiters, handled separately
        result.add(text);
        for (int i = 0; i < delimiters.length; i++) {
            ReifiedList<String> currentResults = new ReifiedArrayList<String>(String.class);

            for (String part : result) {
                String[] splitParts = part.split(delimiters[i]);
                for (String splitPart : splitParts)
                    currentResults.add(splitPart);
            }

            result = currentResults;
        }

        switch (options) {
        case None:
            return result.toArray();
        case RemoveEmptyEntries:
            return Linq.toArray(Linq.where(result, isNotNullOrEmpty()), String.class);
        default:
            throw new IllegalArgumentException("Unrecognized string split option: " + options);
        }
    }

    /**
     * Splits a string by finding consecutive 'tags' i.e. delimiters. E.g. for 0d1h2m3s, using "d,h,m,s" as delimiters would return { 0,1,2,3
     * }. Delimiters that are not found will be ignored. E.g. for 0d1h2m3s, using "d,m,h,s" as delimiters would return { 0,1h2,3 } (i.e. h not
     * found after m). Uses Ordinal string comparison. Does not continuously split in the same way split() does, uses anchor points instead.
     * When splitting strings, Ordinal string comparison is always used.
     * 
     * @throws NullPointerException An argument is null.
     * @throws IllegalArgumentException A delimiter is null or empty.
     */
    @Validate
    public static String[] splitAnchor(@NotNull String text, @NotNull final Iterable<String> delimiters) {
        // parts are stored here
        val result = new ReifiedArrayList<String>(String.class);

        // process delimiters serially
        for (String delim : delimiters) {
            if (isNullOrEmpty(delim))
                throw new IllegalArgumentException("A delimiter cannot be null or empty!");

            if (!isNullOrEmpty(text)) {
                ReifiedList<String> parts = new ReifiedArrayList<String>(split(text, delim));

                if (parts.size() >= 2) {
                    // store if any found
                    result.add(parts.get(0));
                    text = text.substring(parts.get(0).length() + 1);
                }
            }
        }

        return result.toArray();
    }

    /**
     * Splits a string by finding consecutive 'tags' i.e. delimiters. E.g. for 0d1h2m3s, using "d,h,m,s" as delimiters would return { 0,1,2,3
     * }. Delimiters that are not found will be ignored. E.g. for 0d1h2m3s, using "d,m,h,s" as delimiters would return { 0,1h2,3 } (i.e. h not
     * found after m). Uses Ordinal string comparison. Does not continuously split in the same way split() does, uses anchor points instead.
     * When splitting strings, Ordinal string comparison is always used.
     * 
     * @throws NullPointerException An argument is null.
     * @throws IllegalArgumentException A delimiter is null or empty.
     */
    @Validate
    public static String[] splitAnchor(String text, @NotNull final String[] delimiters) {
        return splitAnchor(text, Arrays.asList(delimiters));
    }

    /**
     * Returns true if the value starts with a prefix.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static boolean startsWith(@NotNull final String value, char prefix) {
        if (value.length() == 0)
            return false;

        return value.charAt(0) == prefix;
    }

    /**
     * Returns true if a value starts with a prefix. Uses the CurrentLocale string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    public static boolean startsWith(String value, String prefix) {
        return indexOf(value, prefix, 0, value.length(), StringComparison.CurrentLocale) == 0;
    }

    /**
     * Returns true if a value starts with a prefix. Uses the specified string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    public static boolean startsWith(String value, String prefix, StringComparison stringComparison) {
        return indexOf(value, prefix, 0, 1, stringComparison) == 0;
    }

    /**
     * Returns true if a value starts with a prefix. Uses a culture-aware higher performance string comparison.
     * 
     * @throws NullPointerException An argument is null.
     */
    public static boolean startsWith(String value, String prefix, Locale locale, Collator collator,
            boolean caseSensitive) {
        return indexOf(value, prefix, 0, 1, locale, collator, caseSensitive) == 0;
    }

    /**
     * Similar to String.Substring of .NET, uses a length instead of endIndex.
     * 
     * @throws NullPointerException An argument is null.
     * @throws IndexOutOfBoundsException The length is out of range.
     */
    public static String substring(String value, int length) {
        return substring(value, 0, length);
    }

    /**
     * Similar to String.Substring of .NET, uses a length instead of endIndex.
     * 
     * @throws NullPointerException An argument is null.
     * @throws IndexOutOfBoundsException The index or length is out of range.
     */
    @Validate
    public static String substring(@NotNull final String value, int startIndex, int length) {
        int valueLen = value.length();
        if (startIndex < 0 || startIndex >= valueLen)
            throw new IndexOutOfBoundsException("startIndex=" + startIndex + " valueLen=" + valueLen);
        if (length < 0 || startIndex + length > valueLen || startIndex + length < 0)
            throw new IndexOutOfBoundsException(
                    "length=" + length + " startIndex=" + startIndex + " valueLen=" + valueLen);

        return new String(value.toCharArray(), startIndex, length);
    }

    /**
     * Title-cases a string
     * 
     * @throws NullPointerException An argument is null
     */
    @Validate
    public static String titleCase(@NotNull final String value) {
        val parts = split(value, CONSTANT.WHITESPACE_CHAR, StringSplitOptions.None);
        for (int i = 0; i < parts.length; i++)
            if (parts[i].length() > 0)
                parts[i] = parts[i].substring(0, 1).toUpperCase() + parts[i].substring(1).toLowerCase();

        return delimit(parts, CONSTANT.WHITESPACE);
    }

    /**
     * Returns a character range from start to end (inclusive)
     * 
     * @throws NullPointerException An argument is null
     * @throws IllegalArgumentException When the end is before start
     */
    public static Character[] to(Character start, Character end) {
        return ArrayUtils.box(charRange(start.charValue(), end.charValue() + 1));
    }

    /**
     * Trims a value's beginning of all instances of the given char. Does so repeatedly until no more matches are found.
     * 
     * @throws NullPointerException When an argument is null.
     */
    @Validate
    public static String trimStart(@NotNull final String value, char ch) {
        return trimStart(value, new char[] { ch });
    }

    /**
     * Trims a value's beginning of all the given chars. Does so repeatedly until no more matches are found.
     * 
     * @throws NullPointerException When an argument is null.
     */
    @Validate
    public static String trimStart(@NotNull final String value, @NotNull final char[] chars) {
        int startIndex = 0;
        while (startIndex <= value.length() - 1 && contains(chars, value.charAt(startIndex)))
            startIndex++;

        return value.substring(startIndex);
    }

    /**
     * Trims a value's tail of all the instance of the given char. Does so repeatedly until no more matches are found.
     * 
     * @throws NullPointerException When an argument is null.
     */
    @Validate
    public static String trimEnd(@NotNull final String value, char ch) {
        return trimEnd(value, new char[] { ch });
    }

    /**
     * Trims a value's tail of all the given chars. Does so repeatedly until no more matches are found.
     * 
     * @throws NullPointerException When an argument is null.
     */
    @Validate
    public static String trimEnd(@NotNull final String value, @NotNull final char[] chars) {
        int endIndex = value.length() - 1;
        while (endIndex > 0 && contains(chars, value.charAt(endIndex)))
            endIndex--;

        return value.substring(0, endIndex + 1);
    }

    /**
     * Trims a value of all whitespace chars, i.e. ' ', '\t', '\r', '\n'. Does so repeatedly until no more matches are found.
     * 
     * @throws NullPointerException When an argument is null.
     */
    public static String trim(String value) {
        return trim(value, CONSTANT.WHITESPACE_CHARS);
    }

    /**
     * Trims a value of all the given chars. Does so repeatedly until no more matches are found.
     * 
     * @throws NullPointerException When an argument is null.
     */
    public static String trim(String value, char[] chars) {
        return trimEnd(trimStart(value, chars), chars);
    }

    /**
     * Trims a value using the trimmed string.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static String trimStart(@NotNull String value, @NotNull final String trimmed,
            StringComparison stringComparison) {
        switch (stringComparison) {
        case CurrentLocale:
        case CurrentLocaleIgnoreCase:
        case InvariantLocale:
        case InvariantLocaleIgnoreCase:
            // we must not assume that the length is the same as trimmed.length()
            while (startsWith(value, trimmed, stringComparison))
                // find end position
                for (int i = 0; i <= value.length(); i++)
                    if (equal(trimmed, value.substring(0, i), stringComparison)) {
                        value = value.substring(i);
                        break;
                    }

        case Ordinal:
        case OrdinalIgnoreCase:
            while (startsWith(value, trimmed, stringComparison))
                value = value.substring(trimmed.length());
            break;
        }

        return value;
    }

    /**
     * Trims a value using the trimmed string.
     * 
     * @throws NullPointerException An argument is null.
     */
    @Validate
    public static String trimEnd(@NotNull String value, @NotNull final String trimmed,
            StringComparison stringComparison) {
        switch (stringComparison) {
        case CurrentLocale:
        case CurrentLocaleIgnoreCase:
        case InvariantLocale:
        case InvariantLocaleIgnoreCase:
            // we must not assume that the length is the same as trimmed.length()
            while (endsWith(value, trimmed, stringComparison))
                // find end position
                for (int i = value.length() - 1; i >= 0; i--)
                    if (equal(trimmed, value.substring(i, value.length()))) {
                        value = value.substring(0, i);
                        break;
                    }

        case Ordinal:
        case OrdinalIgnoreCase:
            while (endsWith(value, trimmed, stringComparison))
                value = value.substring(0, value.length() - trimmed.length());
            break;
        }

        return value;
    }

    /**
     * Trims a value using the trimmed string.
     * 
     * @throws NullPointerException An argument is null.
     */
    public static String trim(String value, String trimmed, StringComparison stringComparison) {
        return trimEnd(trimStart(value, trimmed, stringComparison), trimmed, stringComparison);
    }

    /**
     * Cuts the tail of a string, at the specified index.
     * 
     * @throws NullPointerException An argument is null.
     * @throws IndexOutOfBoundsException The index is out of bounds.
     */
    @Validate
    public static String truncate(@NotNull final String value, int endIndex) {
        if (endIndex < 0 || endIndex > value.length())
            throw new IndexOutOfBoundsException("endIndex=" + endIndex + " length=" + value.length());

        return value.substring(0, endIndex);
    }

    /**
     * Cuts the tail of a string, if it exceeds a specified index, otherwise does nothing.
     * 
     * @throws NullPointerException An argument is null.
     * @throws IndexOutOfBoundsException The index is negative.
     */
    @Validate
    public static String truncateIfLonger(@NotNull final String value, int endIndex) {
        if (endIndex < 0)
            throw new IndexOutOfBoundsException("endIndex=" + endIndex);
        if (endIndex > value.length())
            return value;

        return value.substring(0, endIndex);
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<Boolean> tryParseBool(String value) {
        try {
            return new TryResult<Boolean>(parseBool(value));
        } catch (Throwable e) {
            return new TryResult<Boolean>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<Boolean> tryParseBool(String value, String trueValue, String falseValue,
            StringComparison comparisonType) {
        try {
            return new TryResult<Boolean>(parseBool(value, trueValue, falseValue, comparisonType));
        } catch (Throwable e) {
            return new TryResult<Boolean>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<Character> tryParseChar(String value) {
        try {
            return new TryResult<Character>(parseChar(value));
        } catch (Throwable e) {
            return new TryResult<Character>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<Character> tryParseChar(String value, char minValue, char maxValue) {
        try {
            return new TryResult<Character>(parseChar(value, minValue, maxValue));
        } catch (Throwable e) {
            return new TryResult<Character>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<LocalDateTime> tryParseDateTime(String value) {
        try {
            return new TryResult<LocalDateTime>(parseDateTime(value));
        } catch (Throwable e) {
            return new TryResult<LocalDateTime>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<LocalDateTime> tryParseDateTime(String value, DateTimeFormatter fmt) {
        try {
            return new TryResult<LocalDateTime>(parseDateTime(value, fmt));
        } catch (Throwable e) {
            return new TryResult<LocalDateTime>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<LocalDateTime> tryParseDateTime(String value, LocalDateTime minValue,
            LocalDateTime maxValue, DateTimeFormatter fmt) {
        try {
            return new TryResult<LocalDateTime>(parseDateTime(value, minValue, maxValue, fmt));
        } catch (Throwable e) {
            return new TryResult<LocalDateTime>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<BigDecimal> tryParseDecimal(String value) {
        try {
            return new TryResult<BigDecimal>(parseDecimal(value));
        } catch (Throwable e) {
            return new TryResult<BigDecimal>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<BigDecimal> tryParseDecimal(String value, BigDecimal minValue, BigDecimal maxValue) {
        try {
            return new TryResult<BigDecimal>(parseDecimal(value, minValue, maxValue));
        } catch (Throwable e) {
            return new TryResult<BigDecimal>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<Double> tryParseDouble(String value) {
        try {
            return new TryResult<Double>(parseDouble(value));
        } catch (Throwable e) {
            return new TryResult<Double>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<Double> tryParseDouble(String value, double minValue, double maxValue,
            boolean allowInfinity, boolean allowNaN) {
        try {
            return new TryResult<Double>(parseDouble(value, minValue, maxValue, allowInfinity, allowNaN));
        } catch (Throwable e) {
            return new TryResult<Double>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<Float> tryParseFloat(String value) {
        try {
            return new TryResult<Float>(parseFloat(value));
        } catch (Throwable e) {
            return new TryResult<Float>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<Float> tryParseFloat(String value, float minValue, float maxValue,
            boolean allowInfinity, boolean allowNaN) {
        try {
            return new TryResult<Float>(parseFloat(value, minValue, maxValue, allowInfinity, allowNaN));
        } catch (Throwable e) {
            return new TryResult<Float>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<Byte> tryParseInt8(String value) {
        try {
            return new TryResult<Byte>(parseInt8(value));
        } catch (Throwable e) {
            return new TryResult<Byte>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<Byte> tryParseInt8(String value, byte minValue, byte maxValue) {
        try {
            return new TryResult<Byte>(parseInt8(value, minValue, maxValue));
        } catch (Throwable e) {
            return new TryResult<Byte>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<Short> tryParseInt16(String value) {
        try {
            return new TryResult<Short>(parseInt16(value));
        } catch (Throwable e) {
            return new TryResult<Short>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<Short> tryParseInt16(String value, short minValue, short maxValue) {
        try {
            return new TryResult<Short>(parseInt16(value, minValue, maxValue));
        } catch (Throwable e) {
            return new TryResult<Short>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<Integer> tryParseInt32(String value) {
        try {
            return new TryResult<Integer>(parseInt32(value));
        } catch (Throwable e) {
            return new TryResult<Integer>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<Integer> tryParseInt32(String value, int minValue, int maxValue) {
        try {
            return new TryResult<Integer>(parseInt32(value, minValue, maxValue));
        } catch (Throwable e) {
            return new TryResult<Integer>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<Long> tryParseInt64(String value) {
        try {
            return new TryResult<Long>(parseInt64(value));
        } catch (Throwable e) {
            return new TryResult<Long>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<Long> tryParseInt64(String value, long minValue, long maxValue) {
        try {
            return new TryResult<Long>(parseInt64(value, minValue, maxValue));
        } catch (Throwable e) {
            return new TryResult<Long>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<Int128> tryParseInt128(String value) {
        try {
            return new TryResult<Int128>(parseInt128(value));
        } catch (Throwable e) {
            return new TryResult<Int128>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<Int128> tryParseInt128(String value, Int128 minValue, Int128 maxValue) {
        try {
            return new TryResult<Int128>(parseInt128(value, minValue, maxValue));
        } catch (Throwable e) {
            return new TryResult<Int128>();
        }
    }

    /**
     * Tries to parse the given value. Supports IPv4 and IPv6.
     * <p/>
     * Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<InetAddress> tryParseIpAddress(String value) {
        try {
            return new TryResult<InetAddress>(Inet4Address.getByName(value));
        } catch (Throwable e) {
            try {
                return new TryResult<InetAddress>(InetAddress.getByName(value));
            } catch (Throwable e2) {
                return new TryResult<InetAddress>();
            }
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<Duration> tryParseTimeSpan(String value) {
        try {
            return new TryResult<Duration>(parseTimeSpan(value));
        } catch (Throwable e) {
            return new TryResult<Duration>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<Duration> tryParseTimeSpan(String value, Duration minValue, Duration maxValue) {
        try {
            return new TryResult<Duration>(parseTimeSpan(value, minValue, maxValue));
        } catch (Throwable e) {
            return new TryResult<Duration>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<UnsignedByte> tryParseUInt8(String value) {
        try {
            return new TryResult<UnsignedByte>(parseUInt8(value));
        } catch (Throwable e) {
            return new TryResult<UnsignedByte>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<UnsignedByte> tryParseUInt8(String value, UnsignedByte minValue,
            UnsignedByte maxValue) {
        try {
            return new TryResult<UnsignedByte>(parseUInt8(value, minValue, maxValue));
        } catch (Throwable e) {
            return new TryResult<UnsignedByte>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<UnsignedShort> tryParseUInt16(String value) {
        try {
            return new TryResult<UnsignedShort>(parseUInt16(value));
        } catch (Throwable e) {
            return new TryResult<UnsignedShort>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<UnsignedShort> tryParseUInt16(String value, UnsignedShort minValue,
            UnsignedShort maxValue) {
        try {
            return new TryResult<UnsignedShort>(parseUInt16(value, minValue, maxValue));
        } catch (Throwable e) {
            return new TryResult<UnsignedShort>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<UnsignedInteger> tryParseUInt32(String value) {
        try {
            return new TryResult<UnsignedInteger>(parseUInt32(value));
        } catch (Throwable e) {
            return new TryResult<UnsignedInteger>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<UnsignedInteger> tryParseUInt32(String value, UnsignedInteger minValue,
            UnsignedInteger maxValue) {
        try {
            return new TryResult<UnsignedInteger>(parseUInt32(value, minValue, maxValue));
        } catch (Throwable e) {
            return new TryResult<UnsignedInteger>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<UnsignedLong> tryParseUInt64(String value) {
        try {
            return new TryResult<UnsignedLong>(parseUInt64(value));
        } catch (Throwable e) {
            return new TryResult<UnsignedLong>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<UnsignedLong> tryParseUInt64(String value, UnsignedLong minValue,
            UnsignedLong maxValue) {
        try {
            return new TryResult<UnsignedLong>(parseUInt64(value, minValue, maxValue));
        } catch (Throwable e) {
            return new TryResult<UnsignedLong>();
        }
    }

    /**
     * Tries to parse the given value. Does not throw exceptions while parsing under any circumstances.
     */
    public static TryResult<UUID> tryParseUuid(String value) {
        try {
            return new TryResult<UUID>(UUID.fromString(value));
        } catch (Throwable e) {
            return new TryResult<UUID>();
        }
    }

    /**
     * Returns a character range from start (inclusive) to end (exclusive)
     * 
     * @throws NullPointerException An argument is null
     * @throws IllegalArgumentException When the end is before start
     */
    public static Character[] until(Character start, Character end) {
        return ArrayUtils.box(charRange(start.charValue(), end.charValue()));
    }

    /**
     * Returns ISO standard and other frequently used date/time parsers
     */
    private static DateTimeParser[] createCommonDateTimeParsers() {
        return new DateTimeParser[] { ISODateTimeFormat.basicDateTimeNoMillis().getParser(), // yyyyMMdd'T'HHmmssZ
                ISODateTimeFormat.basicDateTime().getParser(), // yyyyMMdd'T'HHmmss.SSSZ
                ISODateTimeFormat.dateHourMinuteSecondFraction().getParser(), // yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS (only 3 ms positions used though)
                ISODateTimeFormat.dateTimeNoMillis().getParser(), // yyyy-MM-dd'T'HH:mm:ssZZ
                ISODateTimeFormat.dateTime().getParser(), // yyyy-MM-dd'T'HH:mm:ss.SSSZZ (ISO 8601)
                DateTimeFormat.forPattern("EEE, dd MMM yyyy HH:mm:ss Z").getParser(), // RFC 2822
                DateTimeFormat.forPattern("yyyy/MM/dd").getParser(),
                DateTimeFormat.forPattern("yyyy/MM/dd HH:mm").getParser(),
                DateTimeFormat.forPattern("yyyy/MM/dd HH:mm:ss").getParser(),
                DateTimeFormat.forPattern("yyyy/MM/dd HH:mm:ss.SSSSSSSSS").getParser(),
                DateTimeFormat.forPattern("yyyy-MM-dd").getParser(),
                DateTimeFormat.forPattern("yyyy-MM-dd HH:mm").getParser(),
                DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss").getParser(),
                DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSSSSSSSS").getParser(),
                DateTimeFormat.forPattern("yyyy.MM.dd").getParser(),
                DateTimeFormat.forPattern("yyyy.MM.dd HH:mm").getParser(),
                DateTimeFormat.forPattern("yyyy.MM.dd HH:mm:ss").getParser(),
                DateTimeFormat.forPattern("yyyy.MM.dd HH:mm:ss.SSSSSSSSS").getParser(),
                DateTimeFormat.forPattern("HH:mm").getParser(), DateTimeFormat.forPattern("HH:mm:ss").getParser(),
                DateTimeFormat.forPattern("HH:mm:ss.SSSSSSSSS").getParser() };
    }

    private StringUtils() {
    }
}