• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */
15 
16 package software.amazon.awssdk.utils;
17 
18 import java.io.UncheckedIOException;
19 import java.nio.ByteBuffer;
20 import java.nio.charset.CharacterCodingException;
21 import java.nio.charset.Charset;
22 import java.util.Locale;
23 import software.amazon.awssdk.annotations.SdkProtectedApi;
24 
25 /**
26  * <p>Operations on {@link java.lang.String} that are
27  * {@code null} safe.</p>
28  *
29  * <ul>
30  *  <li><b>IsEmpty/IsBlank</b>
31  *      - checks if a String contains text</li>
32  *  <li><b>Trim/Strip</b>
33  *      - removes leading and trailing whitespace</li>
34  *  <li><b>Equals/Compare</b>
35  *      - compares two strings null-safe</li>
36  *  <li><b>startsWith</b>
37  *      - check if a String starts with a prefix null-safe</li>
38  *  <li><b>endsWith</b>
39  *      - check if a String ends with a suffix null-safe</li>
40  *  <li><b>IndexOf/LastIndexOf/Contains</b>
41  *      - null-safe index-of checks
42  *  <li><b>IndexOfAny/LastIndexOfAny/IndexOfAnyBut/LastIndexOfAnyBut</b>
43  *      - index-of any of a set of Strings</li>
44  *  <li><b>ContainsOnly/ContainsNone/ContainsAny</b>
45  *      - does String contains only/none/any of these characters</li>
46  *  <li><b>Substring/Left/Right/Mid</b>
47  *      - null-safe substring extractions</li>
48  *  <li><b>SubstringBefore/SubstringAfter/SubstringBetween</b>
49  *      - substring extraction relative to other strings</li>
50  *  <li><b>Split/Join</b>
51  *      - splits a String into an array of substrings and vice versa</li>
52  *  <li><b>Remove/Delete</b>
53  *      - removes part of a String</li>
54  *  <li><b>Replace/Overlay</b>
55  *      - Searches a String and replaces one String with another</li>
56  *  <li><b>Chomp/Chop</b>
57  *      - removes the last part of a String</li>
58  *  <li><b>AppendIfMissing</b>
59  *      - appends a suffix to the end of the String if not present</li>
60  *  <li><b>PrependIfMissing</b>
61  *      - prepends a prefix to the start of the String if not present</li>
62  *  <li><b>LeftPad/RightPad/Center/Repeat</b>
63  *      - pads a String</li>
64  *  <li><b>UpperCase/LowerCase/SwapCase/Capitalize/Uncapitalize</b>
65  *      - changes the case of a String</li>
66  *  <li><b>CountMatches</b>
67  *      - counts the number of occurrences of one String in another</li>
68  *  <li><b>IsAlpha/IsNumeric/IsWhitespace/IsAsciiPrintable</b>
69  *      - checks the characters in a String</li>
70  *  <li><b>DefaultString</b>
71  *      - protects against a null input String</li>
72  *  <li><b>Rotate</b>
73  *      - rotate (circular shift) a String</li>
74  *  <li><b>Reverse/ReverseDelimited</b>
75  *      - reverses a String</li>
76  *  <li><b>Abbreviate</b>
77  *      - abbreviates a string using ellipsis or another given String</li>
78  *  <li><b>Difference</b>
79  *      - compares Strings and reports on their differences</li>
80  *  <li><b>LevenshteinDistance</b>
81  *      - the number of changes needed to change one String into another</li>
82  * </ul>
83  *
84  * <p>The {@code StringUtils} class defines certain words related to
85  * String handling.</p>
86  *
87  * <ul>
88  *  <li>null - {@code null}</li>
89  *  <li>empty - a zero-length string ({@code ""})</li>
90  *  <li>space - the space character ({@code ' '}, char 32)</li>
91  *  <li>whitespace - the characters defined by {@link Character#isWhitespace(char)}</li>
92  *  <li>trim - the characters &lt;= 32 as in {@link String#trim()}</li>
93  * </ul>
94  *
95  * <p>{@code StringUtils} handles {@code null} input Strings quietly.
96  * That is to say that a {@code null} input will return {@code null}.
97  * Where a {@code boolean} or {@code int} is being returned
98  * details vary by method.</p>
99  *
100  * <p>A side effect of the {@code null} handling is that a
101  * {@code NullPointerException} should be considered a bug in
102  * {@code StringUtils}.</p>
103  *
104  * <p>This class's source was modified from the Apache commons-lang library: https://github.com/apache/commons-lang/</p>
105  *
106  * <p>#ThreadSafe#</p>
107  * @see java.lang.String
108  */
109 @SdkProtectedApi
110 public final class StringUtils {
111     // Performance testing notes (JDK 1.4, Jul03, scolebourne)
112     // Whitespace:
113     // Character.isWhitespace() is faster than WHITESPACE.indexOf()
114     // where WHITESPACE is a string of all whitespace characters
115     //
116     // Character access:
117     // String.charAt(n) versus toCharArray(), then array[n]
118     // String.charAt(n) is about 15% worse for a 10K string
119     // They are about equal for a length 50 string
120     // String.charAt(n) is about 4 times better for a length 3 string
121     // String.charAt(n) is best bet overall
122     //
123     // Append:
124     // String.concat about twice as fast as StringBuffer.append
125     // (not sure who tested this)
126 
127     /**
128      * The empty String {@code ""}.
129      */
130     private static final String EMPTY = "";
131 
132     /**
133      * <p>{@code StringUtils} instances should NOT be constructed in
134      * standard programming. Instead, the class should be used as
135      * {@code StringUtils.trim(" foo ");}.</p>
136      */
StringUtils()137     private StringUtils() {
138     }
139 
140     // Empty checks
141     //-----------------------------------------------------------------------
142 
143     /**
144      * <p>Checks if a CharSequence is empty ("") or null.</p>
145      *
146      * <pre>
147      * StringUtils.isEmpty(null)      = true
148      * StringUtils.isEmpty("")        = true
149      * StringUtils.isEmpty(" ")       = false
150      * StringUtils.isEmpty("bob")     = false
151      * StringUtils.isEmpty("  bob  ") = false
152      * </pre>
153      *
154      * <p>NOTE: This method changed in Lang version 2.0.
155      * It no longer trims the CharSequence.
156      * That functionality is available in isBlank().</p>
157      *
158      * @param cs  the CharSequence to check, may be null
159      * @return {@code true} if the CharSequence is empty or null
160      * @since 3.0 Changed signature from isEmpty(String) to isEmpty(CharSequence)
161      */
isEmpty(final CharSequence cs)162     public static boolean isEmpty(final CharSequence cs) {
163         return cs == null || cs.length() == 0;
164     }
165 
166     /**
167      * <p>Checks if a CharSequence is empty (""), null or whitespace only.</p>
168      *
169      * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
170      *
171      * <pre>
172      * StringUtils.isBlank(null)      = true
173      * StringUtils.isBlank("")        = true
174      * StringUtils.isBlank(" ")       = true
175      * StringUtils.isBlank("bob")     = false
176      * StringUtils.isBlank("  bob  ") = false
177      * </pre>
178      *
179      * @param cs  the CharSequence to check, may be null
180      * @return {@code true} if the CharSequence is null, empty or whitespace only
181      * @since 2.0
182      * @since 3.0 Changed signature from isBlank(String) to isBlank(CharSequence)
183      */
isBlank(final CharSequence cs)184     public static boolean isBlank(final CharSequence cs) {
185         if (cs == null || cs.length() == 0) {
186             return true;
187         }
188         for (int i = 0; i < cs.length(); i++) {
189             if (!Character.isWhitespace(cs.charAt(i))) {
190                 return false;
191             }
192         }
193         return true;
194     }
195 
196     /**
197      * <p>Checks if a CharSequence is not empty (""), not null and not whitespace only.</p>
198      *
199      * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
200      *
201      * <pre>
202      * StringUtils.isNotBlank(null)      = false
203      * StringUtils.isNotBlank("")        = false
204      * StringUtils.isNotBlank(" ")       = false
205      * StringUtils.isNotBlank("bob")     = true
206      * StringUtils.isNotBlank("  bob  ") = true
207      * </pre>
208      *
209      * @param cs  the CharSequence to check, may be null
210      * @return {@code true} if the CharSequence is not empty and not null and not whitespace only
211      * @since 2.0
212      * @since 3.0 Changed signature from isNotBlank(String) to isNotBlank(CharSequence)
213      */
isNotBlank(final CharSequence cs)214     public static boolean isNotBlank(final CharSequence cs) {
215         return !isBlank(cs);
216     }
217 
218     // Trim
219     //-----------------------------------------------------------------------
220 
221     /**
222      * <p>Removes control characters (char &lt;= 32) from both
223      * ends of this String, handling {@code null} by returning
224      * {@code null}.</p>
225      *
226      * <p>The String is trimmed using {@link String#trim()}.
227      * Trim removes start and end characters &lt;= 32.</p>
228      *
229      * <pre>
230      * StringUtils.trim(null)          = null
231      * StringUtils.trim("")            = ""
232      * StringUtils.trim("     ")       = ""
233      * StringUtils.trim("abc")         = "abc"
234      * StringUtils.trim("    abc    ") = "abc"
235      * </pre>
236      *
237      * @param str  the String to be trimmed, may be null
238      * @return the trimmed string, {@code null} if null String input
239      */
trim(final String str)240     public static String trim(final String str) {
241         return str == null ? null : str.trim();
242     }
243 
244     /**
245      * <p>Removes control characters (char &lt;= 32) from both
246      * ends of this String returning {@code null} if the String is
247      * empty ("") after the trim or if it is {@code null}.
248      *
249      * <p>The String is trimmed using {@link String#trim()}.
250      * Trim removes start and end characters &lt;= 32.</p>
251      *
252      * <pre>
253      * StringUtils.trimToNull(null)          = null
254      * StringUtils.trimToNull("")            = null
255      * StringUtils.trimToNull("     ")       = null
256      * StringUtils.trimToNull("abc")         = "abc"
257      * StringUtils.trimToNull("    abc    ") = "abc"
258      * </pre>
259      *
260      * @param str  the String to be trimmed, may be null
261      * @return the trimmed String,
262      *  {@code null} if only chars &lt;= 32, empty or null String input
263      * @since 2.0
264      */
trimToNull(final String str)265     public static String trimToNull(final String str) {
266         String ts = trim(str);
267         return isEmpty(ts) ? null : ts;
268     }
269 
270     /**
271      * <p>Removes control characters (char &lt;= 32) from both
272      * ends of this String returning an empty String ("") if the String
273      * is empty ("") after the trim or if it is {@code null}.
274      *
275      * <p>The String is trimmed using {@link String#trim()}.
276      * Trim removes start and end characters &lt;= 32.</p>
277      *
278      * <pre>
279      * StringUtils.trimToEmpty(null)          = ""
280      * StringUtils.trimToEmpty("")            = ""
281      * StringUtils.trimToEmpty("     ")       = ""
282      * StringUtils.trimToEmpty("abc")         = "abc"
283      * StringUtils.trimToEmpty("    abc    ") = "abc"
284      * </pre>
285      *
286      * @param str  the String to be trimmed, may be null
287      * @return the trimmed String, or an empty String if {@code null} input
288      * @since 2.0
289      */
trimToEmpty(final String str)290     public static String trimToEmpty(final String str) {
291         return str == null ? EMPTY : str.trim();
292     }
293 
294     // Equals
295     //-----------------------------------------------------------------------
296 
297     /**
298      * <p>Compares two Strings, returning {@code true} if they represent
299      * equal sequences of characters.</p>
300      *
301      * <p>{@code null}s are handled without exceptions. Two {@code null}
302      * references are considered to be equal. The comparison is case sensitive.</p>
303      *
304      * <pre>
305      * StringUtils.equals(null, null)   = true
306      * StringUtils.equals(null, "abc")  = false
307      * StringUtils.equals("abc", null)  = false
308      * StringUtils.equals("abc", "abc") = true
309      * StringUtils.equals("abc", "ABC") = false
310      * </pre>
311      *
312      * @see Object#equals(Object)
313      * @param cs1  the first String, may be {@code null}
314      * @param cs2  the second String, may be {@code null}
315      * @return {@code true} if the Strings are equal (case-sensitive), or both {@code null}
316      */
equals(final String cs1, final String cs2)317     public static boolean equals(final String cs1, final String cs2) {
318         if (cs1 == null || cs2 == null) {
319             return false;
320         }
321         if (cs1.length() != cs2.length()) {
322             return false;
323         }
324         return cs1.equals(cs2);
325     }
326 
327     // Substring
328     //-----------------------------------------------------------------------
329 
330     /**
331      * <p>Gets a substring from the specified String avoiding exceptions.</p>
332      *
333      * <p>A negative start position can be used to start {@code n}
334      * characters from the end of the String.</p>
335      *
336      * <p>A {@code null} String will return {@code null}.
337      * An empty ("") String will return "".</p>
338      *
339      * <pre>
340      * StringUtils.substring(null, *)   = null
341      * StringUtils.substring("", *)     = ""
342      * StringUtils.substring("abc", 0)  = "abc"
343      * StringUtils.substring("abc", 2)  = "c"
344      * StringUtils.substring("abc", 4)  = ""
345      * StringUtils.substring("abc", -2) = "bc"
346      * StringUtils.substring("abc", -4) = "abc"
347      * </pre>
348      *
349      * @param str  the String to get the substring from, may be null
350      * @param start  the position to start from, negative means count back from the end of the String by this many characters
351      * @return substring from start position, {@code null} if null String input
352      */
substring(final String str, int start)353     public static String substring(final String str, int start) {
354         if (str == null) {
355             return null;
356         }
357 
358         // handle negatives, which means last n characters
359         if (start < 0) {
360             start = str.length() + start; // remember start is negative
361         }
362 
363         if (start < 0) {
364             start = 0;
365         }
366         if (start > str.length()) {
367             return EMPTY;
368         }
369 
370         return str.substring(start);
371     }
372 
373     /**
374      * <p>Gets a substring from the specified String avoiding exceptions.</p>
375      *
376      * <p>A negative start position can be used to start/end {@code n}
377      * characters from the end of the String.</p>
378      *
379      * <p>The returned substring starts with the character in the {@code start}
380      * position and ends before the {@code end} position. All position counting is
381      * zero-based -- i.e., to start at the beginning of the string use
382      * {@code start = 0}. Negative start and end positions can be used to
383      * specify offsets relative to the end of the String.</p>
384      *
385      * <p>If {@code start} is not strictly to the left of {@code end}, ""
386      * is returned.</p>
387      *
388      * <pre>
389      * StringUtils.substring(null, *, *)    = null
390      * StringUtils.substring("", * ,  *)    = "";
391      * StringUtils.substring("abc", 0, 2)   = "ab"
392      * StringUtils.substring("abc", 2, 0)   = ""
393      * StringUtils.substring("abc", 2, 4)   = "c"
394      * StringUtils.substring("abc", 4, 6)   = ""
395      * StringUtils.substring("abc", 2, 2)   = ""
396      * StringUtils.substring("abc", -2, -1) = "b"
397      * StringUtils.substring("abc", -4, 2)  = "ab"
398      * </pre>
399      *
400      * @param str  the String to get the substring from, may be null
401      * @param start  the position to start from, negative means count back from the end of the String by this many characters
402      * @param end  the position to end at (exclusive), negative means count back from the end of the String by this many
403      *             characters
404      * @return substring from start position to end position,
405      *  {@code null} if null String input
406      */
substring(final String str, int start, int end)407     public static String substring(final String str, int start, int end) {
408         if (str == null) {
409             return null;
410         }
411 
412         // handle negatives
413         if (end < 0) {
414             end = str.length() + end; // remember end is negative
415         }
416         if (start < 0) {
417             start = str.length() + start; // remember start is negative
418         }
419 
420         // check length next
421         if (end > str.length()) {
422             end = str.length();
423         }
424 
425         // if start is greater than end, return ""
426         if (start > end) {
427             return EMPTY;
428         }
429 
430         if (start < 0) {
431             start = 0;
432         }
433         if (end < 0) {
434             end = 0;
435         }
436 
437         return str.substring(start, end);
438     }
439 
440     // Case conversion
441     //-----------------------------------------------------------------------
442 
443     /**
444      * <p>Converts a String to upper case as per {@link String#toUpperCase()}.</p>
445      *
446      * <p>A {@code null} input String returns {@code null}.</p>
447      *
448      * <pre>
449      * StringUtils.upperCase(null)  = null
450      * StringUtils.upperCase("")    = ""
451      * StringUtils.upperCase("aBc") = "ABC"
452      * </pre>
453      *
454      * <p>This uses "ENGLISH" as the locale.
455      *
456      * @param str  the String to upper case, may be null
457      * @return the upper cased String, {@code null} if null String input
458      */
upperCase(final String str)459     public static String upperCase(final String str) {
460         if (str == null) {
461             return null;
462         }
463         return str.toUpperCase(Locale.ENGLISH);
464     }
465 
466     /**
467      * <p>Converts a String to lower case as per {@link String#toLowerCase()}.</p>
468      *
469      * <p>A {@code null} input String returns {@code null}.</p>
470      *
471      * <pre>
472      * StringUtils.lowerCase(null)  = null
473      * StringUtils.lowerCase("")    = ""
474      * StringUtils.lowerCase("aBc") = "abc"
475      * </pre>
476      *
477      * <p>This uses "ENGLISH" as the locale.
478      *
479      * @param str  the String to lower case, may be null
480      * @return the lower cased String, {@code null} if null String input
481      */
lowerCase(final String str)482     public static String lowerCase(final String str) {
483         if (str == null) {
484             return null;
485         }
486         return str.toLowerCase(Locale.ENGLISH);
487     }
488 
489     /**
490      * <p>Capitalizes a String changing the first character to title case as
491      * per {@link Character#toTitleCase(int)}. No other characters are changed.</p>
492      *
493      * <pre>
494      * StringUtils.capitalize(null)  = null
495      * StringUtils.capitalize("")    = ""
496      * StringUtils.capitalize("cat") = "Cat"
497      * StringUtils.capitalize("cAt") = "CAt"
498      * StringUtils.capitalize("'cat'") = "'cat'"
499      * </pre>
500      *
501      * @param str the String to capitalize, may be null
502      * @return the capitalized String, {@code null} if null String input
503      * @see #uncapitalize(String)
504      * @since 2.0
505      */
capitalize(final String str)506     public static String capitalize(final String str) {
507         if (str == null || str.length() == 0) {
508             return str;
509         }
510 
511         int firstCodepoint = str.codePointAt(0);
512         int newCodePoint = Character.toTitleCase(firstCodepoint);
513         if (firstCodepoint == newCodePoint) {
514             // already capitalized
515             return str;
516         }
517 
518         int[] newCodePoints = new int[str.length()]; // cannot be longer than the char array
519         int outOffset = 0;
520         newCodePoints[outOffset++] = newCodePoint; // copy the first codepoint
521         for (int inOffset = Character.charCount(firstCodepoint); inOffset < str.length(); ) {
522             int codepoint = str.codePointAt(inOffset);
523             newCodePoints[outOffset++] = codepoint; // copy the remaining ones
524             inOffset += Character.charCount(codepoint);
525         }
526         return new String(newCodePoints, 0, outOffset);
527     }
528 
529     /**
530      * <p>Uncapitalizes a String, changing the first character to lower case as
531      * per {@link Character#toLowerCase(int)}. No other characters are changed.</p>
532      *
533      * <pre>
534      * StringUtils.uncapitalize(null)  = null
535      * StringUtils.uncapitalize("")    = ""
536      * StringUtils.uncapitalize("cat") = "cat"
537      * StringUtils.uncapitalize("Cat") = "cat"
538      * StringUtils.uncapitalize("CAT") = "cAT"
539      * </pre>
540      *
541      * @param str the String to uncapitalize, may be null
542      * @return the uncapitalized String, {@code null} if null String input
543      * @see #capitalize(String)
544      * @since 2.0
545      */
uncapitalize(final String str)546     public static String uncapitalize(final String str) {
547         if (str == null || str.length() == 0) {
548             return str;
549         }
550 
551         int firstCodepoint = str.codePointAt(0);
552         int newCodePoint = Character.toLowerCase(firstCodepoint);
553         if (firstCodepoint == newCodePoint) {
554             // already capitalized
555             return str;
556         }
557 
558         int[] newCodePoints = new int[str.length()]; // cannot be longer than the char array
559         int outOffset = 0;
560         newCodePoints[outOffset++] = newCodePoint; // copy the first codepoint
561         for (int inOffset = Character.charCount(firstCodepoint); inOffset < str.length(); ) {
562             int codepoint = str.codePointAt(inOffset);
563             newCodePoints[outOffset++] = codepoint; // copy the remaining ones
564             inOffset += Character.charCount(codepoint);
565         }
566         return new String(newCodePoints, 0, outOffset);
567     }
568 
569     /**
570      * Encode the given bytes as a string using the given charset
571      * @throws UncheckedIOException with a {@link CharacterCodingException} as the cause if the bytes cannot be encoded using the
572      * provided charset.
573      */
fromBytes(byte[] bytes, Charset charset)574     public static String fromBytes(byte[] bytes, Charset charset) throws UncheckedIOException {
575         try {
576             return charset.newDecoder().decode(ByteBuffer.wrap(bytes)).toString();
577         } catch (CharacterCodingException e) {
578             throw new UncheckedIOException("Cannot encode string.", e);
579         }
580     }
581 
582     /**
583      * Tests if this string starts with the specified prefix ignoring case considerations.
584      *
585      * @param str the string to be tested
586      * @param prefix the prefix
587      * @return true if the string starts with the prefix ignoring case
588      */
startsWithIgnoreCase(String str, String prefix)589     public static boolean startsWithIgnoreCase(String str, String prefix) {
590         return str.regionMatches(true, 0, prefix, 0, prefix.length());
591     }
592 
593     /**
594      * <p>Replaces a String with another String inside a larger String, once.</p>
595      *
596      * <p>A {@code null} reference passed to this method is a no-op.</p>
597      *
598      * <pre>
599      * StringUtils.replaceOnce(null, *, *)        = null
600      * StringUtils.replaceOnce("", *, *)          = ""
601      * StringUtils.replaceOnce("any", null, *)    = "any"
602      * StringUtils.replaceOnce("any", *, null)    = "any"
603      * StringUtils.replaceOnce("any", "", *)      = "any"
604      * StringUtils.replaceOnce("aba", "a", null)  = "aba"
605      * StringUtils.replaceOnce("aba", "a", "")    = "ba"
606      * StringUtils.replaceOnce("aba", "a", "z")   = "zba"
607      * </pre>
608      *
609      * @see #replace(String text, String searchString, String replacement, int max)
610      * @param text  text to search and replace in, may be null
611      * @param searchString  the String to search for, may be null
612      * @param replacement  the String to replace with, may be null
613      * @return the text with any replacements processed,
614      *  {@code null} if null String input
615      */
replaceOnce(String text, String searchString, String replacement)616     public static String replaceOnce(String text, String searchString, String replacement) {
617         return replace(text, searchString, replacement, 1);
618     }
619 
620     /**
621      * <p>Replaces a String with another String inside a larger String,
622      * for the first {@code max} values of the search String,
623      * case sensitively/insensitively based on {@code ignoreCase} value.</p>
624      *
625      * <p>A {@code null} reference passed to this method is a no-op.</p>
626      *
627      * <pre>
628      * StringUtils.replace(null, *, *, *, false)         = null
629      * StringUtils.replace("", *, *, *, false)           = ""
630      * StringUtils.replace("any", null, *, *, false)     = "any"
631      * StringUtils.replace("any", *, null, *, false)     = "any"
632      * StringUtils.replace("any", "", *, *, false)       = "any"
633      * StringUtils.replace("any", *, *, 0, false)        = "any"
634      * StringUtils.replace("abaa", "a", null, -1, false) = "abaa"
635      * StringUtils.replace("abaa", "a", "", -1, false)   = "b"
636      * StringUtils.replace("abaa", "a", "z", 0, false)   = "abaa"
637      * StringUtils.replace("abaa", "A", "z", 1, false)   = "abaa"
638      * StringUtils.replace("abaa", "A", "z", 1, true)   = "zbaa"
639      * StringUtils.replace("abAa", "a", "z", 2, true)   = "zbza"
640      * StringUtils.replace("abAa", "a", "z", -1, true)  = "zbzz"
641      * </pre>
642      *
643      * @param text  text to search and replace in, may be null
644      * @param searchString  the String to search for (case insensitive), may be null
645      * @param replacement  the String to replace it with, may be null
646      * @return the text with any replacements processed,
647      *  {@code null} if null String input
648      */
replace(String text, String searchString, String replacement)649     public static String replace(String text, String searchString, String replacement) {
650         return replace(text, searchString, replacement, -1);
651     }
652 
653     /**
654      * <p>Replaces a String with another String inside a larger String,
655      * for the first {@code max} values of the search String,
656      * case sensitively/insensitively based on {@code ignoreCase} value.</p>
657      *
658      * <p>A {@code null} reference passed to this method is a no-op.</p>
659      *
660      * <pre>
661      * StringUtils.replace(null, *, *, *, false)         = null
662      * StringUtils.replace("", *, *, *, false)           = ""
663      * StringUtils.replace("any", null, *, *, false)     = "any"
664      * StringUtils.replace("any", *, null, *, false)     = "any"
665      * StringUtils.replace("any", "", *, *, false)       = "any"
666      * StringUtils.replace("any", *, *, 0, false)        = "any"
667      * StringUtils.replace("abaa", "a", null, -1, false) = "abaa"
668      * StringUtils.replace("abaa", "a", "", -1, false)   = "b"
669      * StringUtils.replace("abaa", "a", "z", 0, false)   = "abaa"
670      * StringUtils.replace("abaa", "A", "z", 1, false)   = "abaa"
671      * StringUtils.replace("abaa", "A", "z", 1, true)   = "zbaa"
672      * StringUtils.replace("abAa", "a", "z", 2, true)   = "zbza"
673      * StringUtils.replace("abAa", "a", "z", -1, true)  = "zbzz"
674      * </pre>
675      *
676      * @param text  text to search and replace in, may be null
677      * @param searchString  the String to search for (case insensitive), may be null
678      * @param replacement  the String to replace it with, may be null
679      * @param max  maximum number of values to replace, or {@code -1} if no maximum
680      * @return the text with any replacements processed,
681      *  {@code null} if null String input
682      */
replace(String text, String searchString, String replacement, int max)683     private static String replace(String text, String searchString, String replacement, int max) {
684         if (isEmpty(text) || isEmpty(searchString) || replacement == null || max == 0) {
685             return text;
686         }
687         int start = 0;
688         int end = indexOf(text, searchString, start);
689         if (end == -1) {
690             return text;
691         }
692         int replLength = searchString.length();
693         int increase = Math.max(replacement.length() - replLength, 0);
694         increase *= max < 0 ? 16 : Math.min(max, 64);
695         StringBuilder buf = new StringBuilder(text.length() + increase);
696         while (end != -1) {
697             buf.append(text, start, end).append(replacement);
698             start = end + replLength;
699             if (--max == 0) {
700                 break;
701             }
702             end = indexOf(text, searchString, start);
703         }
704         buf.append(text, start, text.length());
705         return buf.toString();
706     }
707 
708     /**
709      * <p>
710      * Replaces all occurrences of Strings within another String.
711      * </p>
712      *
713      * <p>
714      * A {@code null} reference passed to this method is a no-op, or if
715      * any "search string" or "string to replace" is null, that replace will be
716      * ignored. This will not repeat. For repeating replaces, call the
717      * overloaded method.
718      * </p>
719      *
720      * <pre>
721      *  StringUtils.replaceEach(null, *, *)        = null
722      *  StringUtils.replaceEach("", *, *)          = ""
723      *  StringUtils.replaceEach("aba", null, null) = "aba"
724      *  StringUtils.replaceEach("aba", new String[0], null) = "aba"
725      *  StringUtils.replaceEach("aba", null, new String[0]) = "aba"
726      *  StringUtils.replaceEach("aba", new String[]{"a"}, null)  = "aba"
727      *  StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""})  = "b"
728      *  StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"})  = "aba"
729      *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"})  = "wcte"
730      *  (example of how it does not repeat)
731      *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"})  = "dcte"
732      * </pre>
733      *
734      * @param text
735      *            text to search and replace in, no-op if null
736      * @param searchList
737      *            the Strings to search for, no-op if null
738      * @param replacementList
739      *            the Strings to replace them with, no-op if null
740      * @return the text with any replacements processed, {@code null} if
741      *         null String input
742      * @throws IllegalArgumentException
743      *             if the lengths of the arrays are not the same (null is ok,
744      *             and/or size 0)
745      * @since 2.4
746      */
747     public static String replaceEach(String text, String[] searchList, String[] replacementList) {
748         // mchyzer Performance note: This creates very few new objects (one major goal)
749         // let me know if there are performance requests, we can create a harness to measure
750 
751         if (isEmpty(text)) {
752             return text;
753         }
754 
755         int searchLength = searchList.length;
756         int replacementLength = replacementList.length;
757 
758         // make sure lengths are ok, these need to be equal
759         if (searchLength != replacementLength) {
760             throw new IllegalArgumentException("Search and Replace array lengths don't match: "
761                                                + searchLength
762                                                + " vs "
763                                                + replacementLength);
764         }
765 
766         // keep track of which still have matches
767         boolean[] noMoreMatchesForReplIndex = new boolean[searchLength];
768 
769         // index on index that the match was found
770         int textIndex = -1;
771         int replaceIndex = -1;
772         int tempIndex;
773 
774         // index of replace array that will replace the search string found
775         // NOTE: logic duplicated below START
776         for (int i = 0; i < searchLength; i++) {
777             if (noMoreMatchesForReplIndex[i] || isEmpty(searchList[i]) || replacementList[i] == null) {
778                 continue;
779             }
780             tempIndex = text.indexOf(searchList[i]);
781 
782             // see if we need to keep searching for this
783             if (tempIndex == -1) {
784                 noMoreMatchesForReplIndex[i] = true;
785             } else if (textIndex == -1 || tempIndex < textIndex) {
786                 textIndex = tempIndex;
787                 replaceIndex = i;
788             }
789         }
790         // NOTE: logic mostly below END
791 
792         // no search strings found, we are done
793         if (textIndex == -1) {
794             return text;
795         }
796 
797         int start = 0;
798 
799         // get a good guess on the size of the result buffer so it doesn't have to double if it goes over a bit
800         int increase = 0;
801 
802         // count the replacement text elements that are larger than their corresponding text being replaced
803         for (int i = 0; i < searchList.length; i++) {
804             if (searchList[i] == null || replacementList[i] == null) {
805                 continue;
806             }
807             int greater = replacementList[i].length() - searchList[i].length();
808             if (greater > 0) {
809                 increase += 3 * greater; // assume 3 matches
810             }
811         }
812         // have upper-bound at 20% increase, then let Java take over
813         increase = Math.min(increase, text.length() / 5);
814 
815         StringBuilder buf = new StringBuilder(text.length() + increase);
816 
817         while (textIndex != -1) {
818 
819             for (int i = start; i < textIndex; i++) {
820                 buf.append(text.charAt(i));
821             }
822             buf.append(replacementList[replaceIndex]);
823 
824             start = textIndex + searchList[replaceIndex].length();
825 
826             textIndex = -1;
827             replaceIndex = -1;
828             // find the next earliest match
829             // NOTE: logic mostly duplicated above START
830             for (int i = 0; i < searchLength; i++) {
831                 if (noMoreMatchesForReplIndex[i] || searchList[i] == null ||
832                     searchList[i].isEmpty() || replacementList[i] == null) {
833                     continue;
834                 }
835                 tempIndex = text.indexOf(searchList[i], start);
836 
837                 // see if we need to keep searching for this
838                 if (tempIndex == -1) {
839                     noMoreMatchesForReplIndex[i] = true;
840                 } else if (textIndex == -1 || tempIndex < textIndex) {
841                     textIndex = tempIndex;
842                     replaceIndex = i;
843                 }
844             }
845             // NOTE: logic duplicated above END
846 
847         }
848         int textLength = text.length();
849         for (int i = start; i < textLength; i++) {
850             buf.append(text.charAt(i));
851         }
852         return buf.toString();
853     }
854 
855     /**
856      * <p>Finds the first index within a CharSequence, handling {@code null}.
857      * This method uses {@link String#indexOf(String, int)} if possible.</p>
858      *
859      * <p>A {@code null} CharSequence will return {@code -1}.</p>
860      *
861      * <pre>
862      * StringUtils.indexOf(null, *)          = -1
863      * StringUtils.indexOf(*, null)          = -1
864      * StringUtils.indexOf("", "")           = 0
865      * StringUtils.indexOf("", *)            = -1 (except when * = "")
866      * StringUtils.indexOf("aabaabaa", "a")  = 0
867      * StringUtils.indexOf("aabaabaa", "b")  = 2
868      * StringUtils.indexOf("aabaabaa", "ab") = 1
869      * StringUtils.indexOf("aabaabaa", "")   = 0
870      * </pre>
871      *
872      * @param seq  the CharSequence to check, may be null
873      * @param searchSeq  the CharSequence to find, may be null
874      * @return the first index of the search CharSequence,
875      *  -1 if no match or {@code null} string input
876      * @since 2.0
877      * @since 3.0 Changed signature from indexOf(String, String) to indexOf(CharSequence, CharSequence)
878      */
879     private static int indexOf(String seq, String searchSeq, int start) {
880         if (seq == null || searchSeq == null) {
881             return -1;
882         }
883         return seq.indexOf(searchSeq, start);
884     }
885 
886     /**
887      * Replace the prefix of the string provided ignoring case considerations.
888      *
889      * <p>
890      * The unmatched part is unchanged.
891      *
892      *
893      * @param str the string to replace
894      * @param prefix the prefix to find
895      * @param replacement the replacement
896      * @return the replaced string
897      */
898     public static String replacePrefixIgnoreCase(String str, String prefix, String replacement) {
899         return str.replaceFirst("(?i)" + prefix, replacement);
900     }
901 
902 
903     /**
904      * Searches a string for the first occurrence of a character specified by a list of characters.
905      * @param s The string to search.
906      * @param charsToMatch A list of characters to search the string for.
907      * @return The character that was first matched in the string or null if none of the characters were found.
908      */
909     public static Character findFirstOccurrence(String s, char ...charsToMatch) {
910         int lowestIndex = Integer.MAX_VALUE;
911 
912         for (char toMatch : charsToMatch) {
913             int currentIndex = s.indexOf(toMatch);
914             if (currentIndex != -1 && currentIndex < lowestIndex) {
915                 lowestIndex = currentIndex;
916             }
917         }
918 
919         return lowestIndex == Integer.MAX_VALUE ? null : s.charAt(lowestIndex);
920     }
921 
922     /**
923      * Convert a string to boolean safely (as opposed to the less strict {@link Boolean#parseBoolean(String)}). If a customer
924      * specifies a boolean value it should be "true" or "false" (case insensitive) or an exception will be thrown.
925      */
926     public static boolean safeStringToBoolean(String value) {
927         if (value.equalsIgnoreCase("true")) {
928             return true;
929         } else if (value.equalsIgnoreCase("false")) {
930             return false;
931         }
932 
933         throw new IllegalArgumentException("Value was defined as '" + value + "', but should be 'false' or 'true'");
934     }
935 
936     /**
937      * Returns a string whose value is the concatenation of this string repeated {@code count} times.
938      * <p>
939      * If this string is empty or count is zero then the empty string is returned.
940      * <p>
941      * Logical clone of JDK11's {@link String#repeat(int)}.
942      *
943      * @param value the string to repeat
944      * @param count number of times to repeat
945      * @return A string composed of this string repeated {@code count} times or the empty string if this string is empty or count
946      * is zero
947      * @throws IllegalArgumentException if the {@code count} is negative.
948      */
949     public static String repeat(String value, int count) {
950         if (count < 0) {
951             throw new IllegalArgumentException("count is negative: " + count);
952         }
953         if (value == null || value.length() == 0 || count == 1) {
954             return value;
955         }
956         if (count == 0) {
957             return "";
958         }
959         if (value.length() > Integer.MAX_VALUE / count) {
960             throw new OutOfMemoryError("Repeating " + value.length() + " bytes String " + count +
961                                        " times will produce a String exceeding maximum size.");
962         }
963         int len = value.length();
964         int limit = len * count;
965         char[] array = new char[limit];
966         value.getChars(0, len, array, 0);
967         int copied;
968         for (copied = len; copied < limit - copied; copied <<= 1) {
969             System.arraycopy(array, 0, array, copied, copied);
970         }
971         System.arraycopy(array, 0, array, copied, limit - copied);
972         return new String(array);
973     }
974 }