All Downloads are FREE. Search and download functionalities are using the official Maven repository.

software.amazon.awssdk.utils.StringUtils Maven / Gradle / Ivy

/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

package software.amazon.awssdk.utils;

import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.util.Locale;
import software.amazon.awssdk.annotations.SdkProtectedApi;

/**
 * 

Operations on {@link java.lang.String} that are * {@code null} safe.

* *
    *
  • IsEmpty/IsBlank * - checks if a String contains text
  • *
  • Trim/Strip * - removes leading and trailing whitespace
  • *
  • Equals/Compare * - compares two strings null-safe
  • *
  • startsWith * - check if a String starts with a prefix null-safe
  • *
  • endsWith * - check if a String ends with a suffix null-safe
  • *
  • IndexOf/LastIndexOf/Contains * - null-safe index-of checks *
  • IndexOfAny/LastIndexOfAny/IndexOfAnyBut/LastIndexOfAnyBut * - index-of any of a set of Strings
  • *
  • ContainsOnly/ContainsNone/ContainsAny * - does String contains only/none/any of these characters
  • *
  • Substring/Left/Right/Mid * - null-safe substring extractions
  • *
  • SubstringBefore/SubstringAfter/SubstringBetween * - substring extraction relative to other strings
  • *
  • Split/Join * - splits a String into an array of substrings and vice versa
  • *
  • Remove/Delete * - removes part of a String
  • *
  • Replace/Overlay * - Searches a String and replaces one String with another
  • *
  • Chomp/Chop * - removes the last part of a String
  • *
  • AppendIfMissing * - appends a suffix to the end of the String if not present
  • *
  • PrependIfMissing * - prepends a prefix to the start of the String if not present
  • *
  • LeftPad/RightPad/Center/Repeat * - pads a String
  • *
  • UpperCase/LowerCase/SwapCase/Capitalize/Uncapitalize * - changes the case of a String
  • *
  • CountMatches * - counts the number of occurrences of one String in another
  • *
  • IsAlpha/IsNumeric/IsWhitespace/IsAsciiPrintable * - checks the characters in a String
  • *
  • DefaultString * - protects against a null input String
  • *
  • Rotate * - rotate (circular shift) a String
  • *
  • Reverse/ReverseDelimited * - reverses a String
  • *
  • Abbreviate * - abbreviates a string using ellipsis or another given String
  • *
  • Difference * - compares Strings and reports on their differences
  • *
  • LevenshteinDistance * - the number of changes needed to change one String into another
  • *
* *

The {@code StringUtils} class defines certain words related to * String handling.

* *
    *
  • null - {@code null}
  • *
  • empty - a zero-length string ({@code ""})
  • *
  • space - the space character ({@code ' '}, char 32)
  • *
  • whitespace - the characters defined by {@link Character#isWhitespace(char)}
  • *
  • trim - the characters <= 32 as in {@link String#trim()}
  • *
* *

{@code StringUtils} handles {@code null} input Strings quietly. * That is to say that a {@code null} input will return {@code null}. * Where a {@code boolean} or {@code int} is being returned * details vary by method.

* *

A side effect of the {@code null} handling is that a * {@code NullPointerException} should be considered a bug in * {@code StringUtils}.

* *

This class's source was modified from the Apache commons-lang library: https://github.com/apache/commons-lang/

* *

#ThreadSafe#

* @see java.lang.String */ @SdkProtectedApi public final class StringUtils { // Performance testing notes (JDK 1.4, Jul03, scolebourne) // Whitespace: // Character.isWhitespace() is faster than WHITESPACE.indexOf() // where WHITESPACE is a string of all whitespace characters // // Character access: // String.charAt(n) versus toCharArray(), then array[n] // String.charAt(n) is about 15% worse for a 10K string // They are about equal for a length 50 string // String.charAt(n) is about 4 times better for a length 3 string // String.charAt(n) is best bet overall // // Append: // String.concat about twice as fast as StringBuffer.append // (not sure who tested this) /** * The empty String {@code ""}. */ private static final String EMPTY = ""; /** *

{@code StringUtils} instances should NOT be constructed in * standard programming. Instead, the class should be used as * {@code StringUtils.trim(" foo ");}.

*/ private StringUtils() { } // Empty checks //----------------------------------------------------------------------- /** *

Checks if a CharSequence is empty ("") or null.

* *
     * StringUtils.isEmpty(null)      = true
     * StringUtils.isEmpty("")        = true
     * StringUtils.isEmpty(" ")       = false
     * StringUtils.isEmpty("bob")     = false
     * StringUtils.isEmpty("  bob  ") = false
     * 
* *

NOTE: This method changed in Lang version 2.0. * It no longer trims the CharSequence. * That functionality is available in isBlank().

* * @param cs the CharSequence to check, may be null * @return {@code true} if the CharSequence is empty or null * @since 3.0 Changed signature from isEmpty(String) to isEmpty(CharSequence) */ public static boolean isEmpty(final CharSequence cs) { return cs == null || cs.length() == 0; } /** *

Checks if a CharSequence is empty (""), null or whitespace only.

* *

Whitespace is defined by {@link Character#isWhitespace(char)}.

* *
     * StringUtils.isBlank(null)      = true
     * StringUtils.isBlank("")        = true
     * StringUtils.isBlank(" ")       = true
     * StringUtils.isBlank("bob")     = false
     * StringUtils.isBlank("  bob  ") = false
     * 
* * @param cs the CharSequence to check, may be null * @return {@code true} if the CharSequence is null, empty or whitespace only * @since 2.0 * @since 3.0 Changed signature from isBlank(String) to isBlank(CharSequence) */ public static boolean isBlank(final CharSequence cs) { if (cs == null || cs.length() == 0) { return true; } for (int i = 0; i < cs.length(); i++) { if (!Character.isWhitespace(cs.charAt(i))) { return false; } } return true; } /** *

Checks if a CharSequence is not empty (""), not null and not whitespace only.

* *

Whitespace is defined by {@link Character#isWhitespace(char)}.

* *
     * StringUtils.isNotBlank(null)      = false
     * StringUtils.isNotBlank("")        = false
     * StringUtils.isNotBlank(" ")       = false
     * StringUtils.isNotBlank("bob")     = true
     * StringUtils.isNotBlank("  bob  ") = true
     * 
* * @param cs the CharSequence to check, may be null * @return {@code true} if the CharSequence is not empty and not null and not whitespace only * @since 2.0 * @since 3.0 Changed signature from isNotBlank(String) to isNotBlank(CharSequence) */ public static boolean isNotBlank(final CharSequence cs) { return !isBlank(cs); } // Trim //----------------------------------------------------------------------- /** *

Removes control characters (char <= 32) from both * ends of this String, handling {@code null} by returning * {@code null}.

* *

The String is trimmed using {@link String#trim()}. * Trim removes start and end characters <= 32.

* *
     * StringUtils.trim(null)          = null
     * StringUtils.trim("")            = ""
     * StringUtils.trim("     ")       = ""
     * StringUtils.trim("abc")         = "abc"
     * StringUtils.trim("    abc    ") = "abc"
     * 
* * @param str the String to be trimmed, may be null * @return the trimmed string, {@code null} if null String input */ public static String trim(final String str) { return str == null ? null : str.trim(); } /** *

Removes control characters (char <= 32) from both * ends of this String returning {@code null} if the String is * empty ("") after the trim or if it is {@code null}. * *

The String is trimmed using {@link String#trim()}. * Trim removes start and end characters <= 32.

* *
     * StringUtils.trimToNull(null)          = null
     * StringUtils.trimToNull("")            = null
     * StringUtils.trimToNull("     ")       = null
     * StringUtils.trimToNull("abc")         = "abc"
     * StringUtils.trimToNull("    abc    ") = "abc"
     * 
* * @param str the String to be trimmed, may be null * @return the trimmed String, * {@code null} if only chars <= 32, empty or null String input * @since 2.0 */ public static String trimToNull(final String str) { String ts = trim(str); return isEmpty(ts) ? null : ts; } /** *

Removes control characters (char <= 32) from both * ends of this String returning an empty String ("") if the String * is empty ("") after the trim or if it is {@code null}. * *

The String is trimmed using {@link String#trim()}. * Trim removes start and end characters <= 32.

* *
     * StringUtils.trimToEmpty(null)          = ""
     * StringUtils.trimToEmpty("")            = ""
     * StringUtils.trimToEmpty("     ")       = ""
     * StringUtils.trimToEmpty("abc")         = "abc"
     * StringUtils.trimToEmpty("    abc    ") = "abc"
     * 
* * @param str the String to be trimmed, may be null * @return the trimmed String, or an empty String if {@code null} input * @since 2.0 */ public static String trimToEmpty(final String str) { return str == null ? EMPTY : str.trim(); } // Equals //----------------------------------------------------------------------- /** *

Compares two Strings, returning {@code true} if they represent * equal sequences of characters.

* *

{@code null}s are handled without exceptions. Two {@code null} * references are considered to be equal. The comparison is case sensitive.

* *
     * StringUtils.equals(null, null)   = true
     * StringUtils.equals(null, "abc")  = false
     * StringUtils.equals("abc", null)  = false
     * StringUtils.equals("abc", "abc") = true
     * StringUtils.equals("abc", "ABC") = false
     * 
* * @see Object#equals(Object) * @param cs1 the first String, may be {@code null} * @param cs2 the second String, may be {@code null} * @return {@code true} if the Strings are equal (case-sensitive), or both {@code null} */ public static boolean equals(final String cs1, final String cs2) { if (cs1 == null || cs2 == null) { return false; } if (cs1.length() != cs2.length()) { return false; } return cs1.equals(cs2); } // Substring //----------------------------------------------------------------------- /** *

Gets a substring from the specified String avoiding exceptions.

* *

A negative start position can be used to start {@code n} * characters from the end of the String.

* *

A {@code null} String will return {@code null}. * An empty ("") String will return "".

* *
     * StringUtils.substring(null, *)   = null
     * StringUtils.substring("", *)     = ""
     * StringUtils.substring("abc", 0)  = "abc"
     * StringUtils.substring("abc", 2)  = "c"
     * StringUtils.substring("abc", 4)  = ""
     * StringUtils.substring("abc", -2) = "bc"
     * StringUtils.substring("abc", -4) = "abc"
     * 
* * @param str the String to get the substring from, may be null * @param start the position to start from, negative means count back from the end of the String by this many characters * @return substring from start position, {@code null} if null String input */ public static String substring(final String str, int start) { if (str == null) { return null; } // handle negatives, which means last n characters if (start < 0) { start = str.length() + start; // remember start is negative } if (start < 0) { start = 0; } if (start > str.length()) { return EMPTY; } return str.substring(start); } /** *

Gets a substring from the specified String avoiding exceptions.

* *

A negative start position can be used to start/end {@code n} * characters from the end of the String.

* *

The returned substring starts with the character in the {@code start} * position and ends before the {@code end} position. All position counting is * zero-based -- i.e., to start at the beginning of the string use * {@code start = 0}. Negative start and end positions can be used to * specify offsets relative to the end of the String.

* *

If {@code start} is not strictly to the left of {@code end}, "" * is returned.

* *
     * StringUtils.substring(null, *, *)    = null
     * StringUtils.substring("", * ,  *)    = "";
     * StringUtils.substring("abc", 0, 2)   = "ab"
     * StringUtils.substring("abc", 2, 0)   = ""
     * StringUtils.substring("abc", 2, 4)   = "c"
     * StringUtils.substring("abc", 4, 6)   = ""
     * StringUtils.substring("abc", 2, 2)   = ""
     * StringUtils.substring("abc", -2, -1) = "b"
     * StringUtils.substring("abc", -4, 2)  = "ab"
     * 
* * @param str the String to get the substring from, may be null * @param start the position to start from, negative means count back from the end of the String by this many characters * @param end the position to end at (exclusive), negative means count back from the end of the String by this many * characters * @return substring from start position to end position, * {@code null} if null String input */ public static String substring(final String str, int start, int end) { if (str == null) { return null; } // handle negatives if (end < 0) { end = str.length() + end; // remember end is negative } if (start < 0) { start = str.length() + start; // remember start is negative } // check length next if (end > str.length()) { end = str.length(); } // if start is greater than end, return "" if (start > end) { return EMPTY; } if (start < 0) { start = 0; } if (end < 0) { end = 0; } return str.substring(start, end); } // Case conversion //----------------------------------------------------------------------- /** *

Converts a String to upper case as per {@link String#toUpperCase()}.

* *

A {@code null} input String returns {@code null}.

* *
     * StringUtils.upperCase(null)  = null
     * StringUtils.upperCase("")    = ""
     * StringUtils.upperCase("aBc") = "ABC"
     * 
* *

This uses "ENGLISH" as the locale. * * @param str the String to upper case, may be null * @return the upper cased String, {@code null} if null String input */ public static String upperCase(final String str) { if (str == null) { return null; } return str.toUpperCase(Locale.ENGLISH); } /** *

Converts a String to lower case as per {@link String#toLowerCase()}.

* *

A {@code null} input String returns {@code null}.

* *
     * StringUtils.lowerCase(null)  = null
     * StringUtils.lowerCase("")    = ""
     * StringUtils.lowerCase("aBc") = "abc"
     * 
* *

This uses "ENGLISH" as the locale. * * @param str the String to lower case, may be null * @return the lower cased String, {@code null} if null String input */ public static String lowerCase(final String str) { if (str == null) { return null; } return str.toLowerCase(Locale.ENGLISH); } /** *

Capitalizes a String changing the first character to title case as * per {@link Character#toTitleCase(int)}. No other characters are changed.

* *
     * StringUtils.capitalize(null)  = null
     * StringUtils.capitalize("")    = ""
     * StringUtils.capitalize("cat") = "Cat"
     * StringUtils.capitalize("cAt") = "CAt"
     * StringUtils.capitalize("'cat'") = "'cat'"
     * 
* * @param str the String to capitalize, may be null * @return the capitalized String, {@code null} if null String input * @see #uncapitalize(String) * @since 2.0 */ public static String capitalize(final String str) { if (str == null || str.length() == 0) { return str; } int firstCodepoint = str.codePointAt(0); int newCodePoint = Character.toTitleCase(firstCodepoint); if (firstCodepoint == newCodePoint) { // already capitalized return str; } int[] newCodePoints = new int[str.length()]; // cannot be longer than the char array int outOffset = 0; newCodePoints[outOffset++] = newCodePoint; // copy the first codepoint for (int inOffset = Character.charCount(firstCodepoint); inOffset < str.length(); ) { int codepoint = str.codePointAt(inOffset); newCodePoints[outOffset++] = codepoint; // copy the remaining ones inOffset += Character.charCount(codepoint); } return new String(newCodePoints, 0, outOffset); } /** *

Uncapitalizes a String, changing the first character to lower case as * per {@link Character#toLowerCase(int)}. No other characters are changed.

* *
     * StringUtils.uncapitalize(null)  = null
     * StringUtils.uncapitalize("")    = ""
     * StringUtils.uncapitalize("cat") = "cat"
     * StringUtils.uncapitalize("Cat") = "cat"
     * StringUtils.uncapitalize("CAT") = "cAT"
     * 
* * @param str the String to uncapitalize, may be null * @return the uncapitalized String, {@code null} if null String input * @see #capitalize(String) * @since 2.0 */ public static String uncapitalize(final String str) { if (str == null || str.length() == 0) { return str; } int firstCodepoint = str.codePointAt(0); int newCodePoint = Character.toLowerCase(firstCodepoint); if (firstCodepoint == newCodePoint) { // already capitalized return str; } int[] newCodePoints = new int[str.length()]; // cannot be longer than the char array int outOffset = 0; newCodePoints[outOffset++] = newCodePoint; // copy the first codepoint for (int inOffset = Character.charCount(firstCodepoint); inOffset < str.length(); ) { int codepoint = str.codePointAt(inOffset); newCodePoints[outOffset++] = codepoint; // copy the remaining ones inOffset += Character.charCount(codepoint); } return new String(newCodePoints, 0, outOffset); } /** * Encode the given bytes as a string using the given charset * @throws UncheckedIOException with a {@link CharacterCodingException} as the cause if the bytes cannot be encoded using the * provided charset. */ public static String fromBytes(byte[] bytes, Charset charset) throws UncheckedIOException { try { return charset.newDecoder().decode(ByteBuffer.wrap(bytes)).toString(); } catch (CharacterCodingException e) { throw new UncheckedIOException("Cannot encode string.", e); } } /** * Tests if this string starts with the specified prefix ignoring case considerations. * * @param str the string to be tested * @param prefix the prefix * @return true if the string starts with the prefix ignoring case */ public static boolean startsWithIgnoreCase(String str, String prefix) { return str.regionMatches(true, 0, prefix, 0, prefix.length()); } /** *

Replaces a String with another String inside a larger String, once.

* *

A {@code null} reference passed to this method is a no-op.

* *
     * StringUtils.replaceOnce(null, *, *)        = null
     * StringUtils.replaceOnce("", *, *)          = ""
     * StringUtils.replaceOnce("any", null, *)    = "any"
     * StringUtils.replaceOnce("any", *, null)    = "any"
     * StringUtils.replaceOnce("any", "", *)      = "any"
     * StringUtils.replaceOnce("aba", "a", null)  = "aba"
     * StringUtils.replaceOnce("aba", "a", "")    = "ba"
     * StringUtils.replaceOnce("aba", "a", "z")   = "zba"
     * 
* * @see #replace(String text, String searchString, String replacement, int max) * @param text text to search and replace in, may be null * @param searchString the String to search for, may be null * @param replacement the String to replace with, may be null * @return the text with any replacements processed, * {@code null} if null String input */ public static String replaceOnce(String text, String searchString, String replacement) { return replace(text, searchString, replacement, 1); } /** *

Replaces a String with another String inside a larger String, * for the first {@code max} values of the search String, * case sensitively/insensitively based on {@code ignoreCase} value.

* *

A {@code null} reference passed to this method is a no-op.

* *
     * StringUtils.replace(null, *, *, *, false)         = null
     * StringUtils.replace("", *, *, *, false)           = ""
     * StringUtils.replace("any", null, *, *, false)     = "any"
     * StringUtils.replace("any", *, null, *, false)     = "any"
     * StringUtils.replace("any", "", *, *, false)       = "any"
     * StringUtils.replace("any", *, *, 0, false)        = "any"
     * StringUtils.replace("abaa", "a", null, -1, false) = "abaa"
     * StringUtils.replace("abaa", "a", "", -1, false)   = "b"
     * StringUtils.replace("abaa", "a", "z", 0, false)   = "abaa"
     * StringUtils.replace("abaa", "A", "z", 1, false)   = "abaa"
     * StringUtils.replace("abaa", "A", "z", 1, true)   = "zbaa"
     * StringUtils.replace("abAa", "a", "z", 2, true)   = "zbza"
     * StringUtils.replace("abAa", "a", "z", -1, true)  = "zbzz"
     * 
* * @param text text to search and replace in, may be null * @param searchString the String to search for (case insensitive), may be null * @param replacement the String to replace it with, may be null * @return the text with any replacements processed, * {@code null} if null String input */ public static String replace(String text, String searchString, String replacement) { return replace(text, searchString, replacement, -1); } /** *

Replaces a String with another String inside a larger String, * for the first {@code max} values of the search String, * case sensitively/insensitively based on {@code ignoreCase} value.

* *

A {@code null} reference passed to this method is a no-op.

* *
     * StringUtils.replace(null, *, *, *, false)         = null
     * StringUtils.replace("", *, *, *, false)           = ""
     * StringUtils.replace("any", null, *, *, false)     = "any"
     * StringUtils.replace("any", *, null, *, false)     = "any"
     * StringUtils.replace("any", "", *, *, false)       = "any"
     * StringUtils.replace("any", *, *, 0, false)        = "any"
     * StringUtils.replace("abaa", "a", null, -1, false) = "abaa"
     * StringUtils.replace("abaa", "a", "", -1, false)   = "b"
     * StringUtils.replace("abaa", "a", "z", 0, false)   = "abaa"
     * StringUtils.replace("abaa", "A", "z", 1, false)   = "abaa"
     * StringUtils.replace("abaa", "A", "z", 1, true)   = "zbaa"
     * StringUtils.replace("abAa", "a", "z", 2, true)   = "zbza"
     * StringUtils.replace("abAa", "a", "z", -1, true)  = "zbzz"
     * 
* * @param text text to search and replace in, may be null * @param searchString the String to search for (case insensitive), may be null * @param replacement the String to replace it with, may be null * @param max maximum number of values to replace, or {@code -1} if no maximum * @return the text with any replacements processed, * {@code null} if null String input */ private static String replace(String text, String searchString, String replacement, int max) { if (isEmpty(text) || isEmpty(searchString) || replacement == null || max == 0) { return text; } int start = 0; int end = indexOf(text, searchString, start); if (end == -1) { return text; } int replLength = searchString.length(); int increase = Math.max(replacement.length() - replLength, 0); increase *= max < 0 ? 16 : Math.min(max, 64); StringBuilder buf = new StringBuilder(text.length() + increase); while (end != -1) { buf.append(text, start, end).append(replacement); start = end + replLength; if (--max == 0) { break; } end = indexOf(text, searchString, start); } buf.append(text, start, text.length()); return buf.toString(); } /** *

* Replaces all occurrences of Strings within another String. *

* *

* A {@code null} reference passed to this method is a no-op, or if * any "search string" or "string to replace" is null, that replace will be * ignored. This will not repeat. For repeating replaces, call the * overloaded method. *

* *
     *  StringUtils.replaceEach(null, *, *)        = null
     *  StringUtils.replaceEach("", *, *)          = ""
     *  StringUtils.replaceEach("aba", null, null) = "aba"
     *  StringUtils.replaceEach("aba", new String[0], null) = "aba"
     *  StringUtils.replaceEach("aba", null, new String[0]) = "aba"
     *  StringUtils.replaceEach("aba", new String[]{"a"}, null)  = "aba"
     *  StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""})  = "b"
     *  StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"})  = "aba"
     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"})  = "wcte"
     *  (example of how it does not repeat)
     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"})  = "dcte"
     * 
* * @param text * text to search and replace in, no-op if null * @param searchList * the Strings to search for, no-op if null * @param replacementList * the Strings to replace them with, no-op if null * @return the text with any replacements processed, {@code null} if * null String input * @throws IllegalArgumentException * if the lengths of the arrays are not the same (null is ok, * and/or size 0) * @since 2.4 */ public static String replaceEach(String text, String[] searchList, String[] replacementList) { // mchyzer Performance note: This creates very few new objects (one major goal) // let me know if there are performance requests, we can create a harness to measure if (isEmpty(text)) { return text; } int searchLength = searchList.length; int replacementLength = replacementList.length; // make sure lengths are ok, these need to be equal if (searchLength != replacementLength) { throw new IllegalArgumentException("Search and Replace array lengths don't match: " + searchLength + " vs " + replacementLength); } // keep track of which still have matches boolean[] noMoreMatchesForReplIndex = new boolean[searchLength]; // index on index that the match was found int textIndex = -1; int replaceIndex = -1; int tempIndex; // index of replace array that will replace the search string found // NOTE: logic duplicated below START for (int i = 0; i < searchLength; i++) { if (noMoreMatchesForReplIndex[i] || isEmpty(searchList[i]) || replacementList[i] == null) { continue; } tempIndex = text.indexOf(searchList[i]); // see if we need to keep searching for this if (tempIndex == -1) { noMoreMatchesForReplIndex[i] = true; } else if (textIndex == -1 || tempIndex < textIndex) { textIndex = tempIndex; replaceIndex = i; } } // NOTE: logic mostly below END // no search strings found, we are done if (textIndex == -1) { return text; } int start = 0; // get a good guess on the size of the result buffer so it doesn't have to double if it goes over a bit int increase = 0; // count the replacement text elements that are larger than their corresponding text being replaced for (int i = 0; i < searchList.length; i++) { if (searchList[i] == null || replacementList[i] == null) { continue; } int greater = replacementList[i].length() - searchList[i].length(); if (greater > 0) { increase += 3 * greater; // assume 3 matches } } // have upper-bound at 20% increase, then let Java take over increase = Math.min(increase, text.length() / 5); StringBuilder buf = new StringBuilder(text.length() + increase); while (textIndex != -1) { for (int i = start; i < textIndex; i++) { buf.append(text.charAt(i)); } buf.append(replacementList[replaceIndex]); start = textIndex + searchList[replaceIndex].length(); textIndex = -1; replaceIndex = -1; // find the next earliest match // NOTE: logic mostly duplicated above START for (int i = 0; i < searchLength; i++) { if (noMoreMatchesForReplIndex[i] || searchList[i] == null || searchList[i].isEmpty() || replacementList[i] == null) { continue; } tempIndex = text.indexOf(searchList[i], start); // see if we need to keep searching for this if (tempIndex == -1) { noMoreMatchesForReplIndex[i] = true; } else if (textIndex == -1 || tempIndex < textIndex) { textIndex = tempIndex; replaceIndex = i; } } // NOTE: logic duplicated above END } int textLength = text.length(); for (int i = start; i < textLength; i++) { buf.append(text.charAt(i)); } return buf.toString(); } /** *

Finds the first index within a CharSequence, handling {@code null}. * This method uses {@link String#indexOf(String, int)} if possible.

* *

A {@code null} CharSequence will return {@code -1}.

* *
     * StringUtils.indexOf(null, *)          = -1
     * StringUtils.indexOf(*, null)          = -1
     * StringUtils.indexOf("", "")           = 0
     * StringUtils.indexOf("", *)            = -1 (except when * = "")
     * StringUtils.indexOf("aabaabaa", "a")  = 0
     * StringUtils.indexOf("aabaabaa", "b")  = 2
     * StringUtils.indexOf("aabaabaa", "ab") = 1
     * StringUtils.indexOf("aabaabaa", "")   = 0
     * 
* * @param seq the CharSequence to check, may be null * @param searchSeq the CharSequence to find, may be null * @return the first index of the search CharSequence, * -1 if no match or {@code null} string input * @since 2.0 * @since 3.0 Changed signature from indexOf(String, String) to indexOf(CharSequence, CharSequence) */ private static int indexOf(String seq, String searchSeq, int start) { if (seq == null || searchSeq == null) { return -1; } return seq.indexOf(searchSeq, start); } /** * Replace the prefix of the string provided ignoring case considerations. * *

* The unmatched part is unchanged. * * * @param str the string to replace * @param prefix the prefix to find * @param replacement the replacement * @return the replaced string */ public static String replacePrefixIgnoreCase(String str, String prefix, String replacement) { return str.replaceFirst("(?i)" + prefix, replacement); } /** * Searches a string for the first occurrence of a character specified by a list of characters. * @param s The string to search. * @param charsToMatch A list of characters to search the string for. * @return The character that was first matched in the string or null if none of the characters were found. */ public static Character findFirstOccurrence(String s, char... charsToMatch) { int lowestIndex = Integer.MAX_VALUE; for (char toMatch : charsToMatch) { int currentIndex = s.indexOf(toMatch); if (currentIndex != -1 && currentIndex < lowestIndex) { lowestIndex = currentIndex; } } return lowestIndex == Integer.MAX_VALUE ? null : s.charAt(lowestIndex); } /** * Convert a string to boolean safely (as opposed to the less strict {@link Boolean#parseBoolean(String)}). If a customer * specifies a boolean value it should be "true" or "false" (case insensitive) or an exception will be thrown. */ public static boolean safeStringToBoolean(String value) { if (value.equalsIgnoreCase("true")) { return true; } else if (value.equalsIgnoreCase("false")) { return false; } throw new IllegalArgumentException("Value was defined as '" + value + "', but should be 'false' or 'true'"); } /** * Returns a string whose value is the concatenation of this string repeated {@code count} times. *

* If this string is empty or count is zero then the empty string is returned. *

* Logical clone of JDK11's {@link String#repeat(int)}. * * @param value the string to repeat * @param count number of times to repeat * @return A string composed of this string repeated {@code count} times or the empty string if this string is empty or count * is zero * @throws IllegalArgumentException if the {@code count} is negative. */ public static String repeat(String value, int count) { if (count < 0) { throw new IllegalArgumentException("count is negative: " + count); } if (value == null || value.length() == 0 || count == 1) { return value; } if (count == 0) { return ""; } if (value.length() > Integer.MAX_VALUE / count) { throw new OutOfMemoryError("Repeating " + value.length() + " bytes String " + count + " times will produce a String exceeding maximum size."); } int len = value.length(); int limit = len * count; char[] array = new char[limit]; value.getChars(0, len, array, 0); int copied; for (copied = len; copied < limit - copied; copied <<= 1) { System.arraycopy(array, 0, array, copied, copied); } System.arraycopy(array, 0, array, copied, limit - copied); return new String(array); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy