001// Generated by delombok at Sat Feb 25 17:58:34 UTC 2023
002package io.cui.tools.string;
003
004import static io.cui.tools.base.Preconditions.checkArgument;
005import java.util.Optional;
006import java.util.function.Predicate;
007import io.cui.tools.logging.CuiLogger;
008import lombok.NonNull;
009
010/**
011 * Provides some extensions to String handling like com.google.common.base.Strings. It was
012 * created using different source, see authors
013 *
014 * @author Oliver Wolff
015 * @author Sven Haag
016 * @author https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/StringUtils.java
017 * @author https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/CharSequenceUtils.java
018 * @author https://github.com/spring-projects/spring-framework/blob/v5.1.8.RELEASE/spring-core/src/main/java/org/springframework/util/StringUtils.java
019 * @author com.google.common.base.Strings
020 */
021public final class MoreStrings {
022    private static final CuiLogger log = new CuiLogger(MoreStrings.class);
023    /**
024     * The empty String {@code ""}.
025     */
026    public static final String EMPTY = "";
027    /**
028     * <p>
029     * The maximum size to which the padding constant(s) can expand.
030     * </p>
031     */
032    private static final int PAD_LIMIT = 8192;
033    /**
034     * A String for a space character.
035     */
036    public static final String SPACE = " ";
037    /**
038     * Represents a failed index search.
039     */
040    public static final int INDEX_NOT_FOUND = -1;
041
042    /**
043     * "Unquotes" a String, saying if the given String starts and ends with the token "'" or """ the
044     * quotes will be stripped
045     *
046     * @param original may be null or empty
047     * @return the unquoted String or the original in none could be found
048     */
049    public static String unquote(final String original) {
050        if (isEmpty(original)) {
051            return original;
052        }
053        if (original.startsWith("\"") && original.endsWith("\"") || original.startsWith("\'") && original.endsWith("\'")) {
054            return original.substring(1, original.length() - 1);
055        }
056        return original;
057    }
058
059    /**
060     * <p>
061     * Checks if the CharSequence contains only lowercase characters.
062     * </p>
063     *
064     * <p>
065     * {@code null} will return {@code false}.
066     * An empty CharSequence (length()=0) will return {@code false}.
067     * </p>
068     *
069     * <pre>
070     * MoreStrings.isAllLowerCase(null)   = false
071     * MoreStrings.isAllLowerCase("")     = false
072     * MoreStrings.isAllLowerCase("  ")   = false
073     * MoreStrings.isAllLowerCase("abc")  = true
074     * MoreStrings.isAllLowerCase("abC")  = false
075     * MoreStrings.isAllLowerCase("ab c") = false
076     * MoreStrings.isAllLowerCase("ab1c") = false
077     * MoreStrings.isAllLowerCase("ab/c") = false
078     * </pre>
079     *
080     * @param cs the CharSequence to check, may be null
081     *
082     * @return {@code true} if only contains lowercase characters, and is non-null
083     * @author https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/StringUtils.java
084     */
085    public static boolean isAllLowerCase(final CharSequence cs) {
086        if (isEmpty(cs)) {
087            return false;
088        }
089        final var sz = cs.length();
090        for (var i = 0; i < sz; i++) {
091            if (!Character.isLowerCase(cs.charAt(i))) {
092                return false;
093            }
094        }
095        return true;
096    }
097
098    /**
099     * <p>
100     * Checks if the CharSequence contains only uppercase characters.
101     * </p>
102     *
103     * <p>
104     * {@code null} will return {@code false}.
105     * An empty String (length()=0) will return {@code false}.
106     * </p>
107     *
108     * <pre>
109     * MoreStrings.isAllUpperCase(null)   = false
110     * MoreStrings.isAllUpperCase("")     = false
111     * MoreStrings.isAllUpperCase("  ")   = false
112     * MoreStrings.isAllUpperCase("ABC")  = true
113     * MoreStrings.isAllUpperCase("aBC")  = false
114     * MoreStrings.isAllUpperCase("A C")  = false
115     * MoreStrings.isAllUpperCase("A1C")  = false
116     * MoreStrings.isAllUpperCase("A/C")  = false
117     * </pre>
118     *
119     * @param cs the CharSequence to check, may be null
120     *
121     * @return {@code true} if only contains uppercase characters, and is non-null
122     * @author https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/StringUtils.java
123     */
124    public static boolean isAllUpperCase(final CharSequence cs) {
125        if (isEmpty(cs)) {
126            return false;
127        }
128        final var sz = cs.length();
129        for (var i = 0; i < sz; i++) {
130            if (!Character.isUpperCase(cs.charAt(i))) {
131                return false;
132            }
133        }
134        return true;
135    }
136
137    /**
138     * <p>
139     * Checks if a CharSequence is empty ("") or null.
140     * </p>
141     *
142     * <pre>
143     * MoreStrings.isEmpty(null)      = true
144     * MoreStrings.isEmpty("")        = true
145     * MoreStrings.isEmpty(" ")       = false
146     * MoreStrings.isEmpty("bob")     = false
147     * MoreStrings.isEmpty("  bob  ") = false
148     * </pre>
149     *
150     * @param cs the CharSequence to check, may be null
151     *
152     * @return {@code true} if the CharSequence is empty or null
153     * @author https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/StringUtils.java
154     */
155    public static boolean isEmpty(final CharSequence cs) {
156        return cs == null || cs.length() == 0;
157    }
158
159    /**
160     * <p>
161     * Checks if a CharSequence is not empty ("") or null.
162     * </p>
163     *
164     * <pre>
165     * MoreStrings.isEmpty(null)      = false
166     * MoreStrings.isEmpty("")        = false
167     * MoreStrings.isEmpty(" ")       = true
168     * </pre>
169     *
170     * @param cs the CharSequence to check, may be null
171     *
172     * @return {@code true} if the CharSequence is not empty
173     */
174    public static boolean isPresent(final CharSequence cs) {
175        return cs != null && cs.length() > 0;
176    }
177
178    /**
179     * <p>
180     * Checks if the CharSequence contains only Unicode digits.
181     * A decimal point is not a Unicode digit and returns false.
182     * </p>
183     *
184     * <p>
185     * {@code null} will return {@code false}.
186     * An empty CharSequence (length()=0) will return {@code false}.
187     * </p>
188     *
189     * <p>
190     * Note that the method does not allow for a leading sign, either positive or negative.
191     * Also, if a String passes the numeric test, it may still generate a NumberFormatException
192     * when parsed by Integer.parseInt or Long.parseLong, e.g. if the value is outside the range
193     * for int or long respectively.
194     * </p>
195     *
196     * <pre>
197     * MoreStrings.isNumeric(null)   = false
198     * MoreStrings.isNumeric("")     = false
199     * MoreStrings.isNumeric("  ")   = false
200     * MoreStrings.isNumeric("123")  = true
201     * MoreStrings.isNumeric("\u0967\u0968\u0969")  = true
202     * MoreStrings.isNumeric("12 3") = false
203     * MoreStrings.isNumeric("ab2c") = false
204     * MoreStrings.isNumeric("12-3") = false
205     * MoreStrings.isNumeric("12.3") = false
206     * MoreStrings.isNumeric("-123") = false
207     * MoreStrings.isNumeric("+123") = false
208     * </pre>
209     *
210     * @param cs the CharSequence to check, may be null
211     *
212     * @return {@code true} if only contains digits, and is non-null
213     * @author https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/StringUtils.java
214     */
215    public static boolean isNumeric(final CharSequence cs) {
216        if (isEmpty(cs)) {
217            return false;
218        }
219        final var sz = cs.length();
220        for (var i = 0; i < sz; i++) {
221            if (!Character.isDigit(cs.charAt(i))) {
222                return false;
223            }
224        }
225        return true;
226    }
227
228    /**
229     * <p>
230     * Checks if a CharSequence is empty (""), null or whitespace only.
231     * </p>
232     *
233     * <p>
234     * Whitespace is defined by {@link Character#isWhitespace(char)}.
235     * </p>
236     *
237     * <pre>
238     * MoreStrings.isBlank(null)      = true
239     * MoreStrings.isBlank("")        = true
240     * MoreStrings.isBlank(" ")       = true
241     * MoreStrings.isBlank("bob")     = false
242     * MoreStrings.isBlank("  bob  ") = false
243     * </pre>
244     *
245     * @param cs the CharSequence to check, may be null
246     *
247     * @return {@code true} if the CharSequence is null, empty or whitespace only
248     * @author https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/StringUtils.java
249     */
250    public static boolean isBlank(final CharSequence cs) {
251        final int strLen;
252        if (cs == null || (strLen = cs.length()) == 0) {
253            return true;
254        }
255        for (var i = 0; i < strLen; i++) {
256            if (!Character.isWhitespace(cs.charAt(i))) {
257                return false;
258            }
259        }
260        return true;
261    }
262
263    /**
264     * {@code NOT} of {@link #isBlank(CharSequence)}.
265     *
266     * <p>
267     * Checks if a CharSequence is not empty (""), null or contains whitespaces only.
268     * </p>
269     *
270     * <p>
271     * Whitespace is defined by {@link Character#isWhitespace(char)}.
272     * </p>
273     *
274     * <pre>
275     * MoreStrings.isBlank(null)      = false
276     * MoreStrings.isBlank("")        = false
277     * MoreStrings.isBlank(" ")       = false
278     * MoreStrings.isBlank("bob")     = true
279     * MoreStrings.isBlank("  bob  ") = true
280     * </pre>
281     *
282     * @param cs to be checked
283     * @return {@code true} if the given string is no blank, {@code false} otherwise
284     */
285    public static boolean isNotBlank(final CharSequence cs) {
286        return !isBlank(cs);
287    }
288
289    /**
290     * <p>
291     * Counts how many times the substring appears in the larger string.
292     * </p>
293     *
294     * <p>
295     * A {@code null} or empty ("") String input returns {@code 0}.
296     * </p>
297     *
298     * <pre>
299     * MoreStrings.countMatches(null, *)       = 0
300     * MoreStrings.countMatches("", *)         = 0
301     * MoreStrings.countMatches("abba", null)  = 0
302     * MoreStrings.countMatches("abba", "")    = 0
303     * MoreStrings.countMatches("abba", "a")   = 2
304     * MoreStrings.countMatches("abba", "ab")  = 1
305     * MoreStrings.countMatches("abba", "xxx") = 0
306     * </pre>
307     *
308     *
309     * @param str the CharSequence to check, may be null
310     * @param sub the substring to count, may be null
311     * @return the number of occurrences, 0 if either CharSequence is {@code null}
312     * @author https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/StringUtils.java
313     */
314    public static int countMatches(final CharSequence str, final CharSequence sub) {
315        if (isEmpty(str) || isEmpty(sub)) {
316            return 0;
317        }
318        var count = 0;
319        var idx = 0;
320        while ((idx = indexOf(str, sub, idx)) != INDEX_NOT_FOUND) {
321            count++;
322            idx += sub.length();
323        }
324        return count;
325    }
326
327    /**
328     * <p>
329     * Left pad a String with spaces (' ').
330     * </p>
331     *
332     * <p>
333     * The String is padded to the size of {@code size}.
334     * </p>
335     *
336     * <pre>
337     * MoreStrings.leftPad(null, *)   = null
338     * MoreStrings.leftPad("", 3)     = "   "
339     * MoreStrings.leftPad("bat", 3)  = "bat"
340     * MoreStrings.leftPad("bat", 5)  = "  bat"
341     * MoreStrings.leftPad("bat", 1)  = "bat"
342     * MoreStrings.leftPad("bat", -1) = "bat"
343     * </pre>
344     *
345     * @param str the String to pad out, may be null
346     * @param size the size to pad to
347     * @return left padded String or original String if no padding is necessary,
348     *         {@code null} if null String input
349     * @author https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/StringUtils.java
350     */
351    public static String leftPad(final String str, final int size) {
352        return leftPad(str, size, ' ');
353    }
354
355    /**
356     * <p>
357     * Left pad a String with a specified character.
358     * </p>
359     *
360     * <p>
361     * Pad to a size of {@code size}.
362     * </p>
363     *
364     * <pre>
365     * MoreStrings.leftPad(null, *, *)     = null
366     * MoreStrings.leftPad("", 3, 'z')     = "zzz"
367     * MoreStrings.leftPad("bat", 3, 'z')  = "bat"
368     * MoreStrings.leftPad("bat", 5, 'z')  = "zzbat"
369     * MoreStrings.leftPad("bat", 1, 'z')  = "bat"
370     * MoreStrings.leftPad("bat", -1, 'z') = "bat"
371     * </pre>
372     *
373     * @param str the String to pad out, may be null
374     * @param size the size to pad to
375     * @param padChar the character to pad with
376     * @return left padded String or original String if no padding is necessary,
377     *         {@code null} if null String input
378     * @author https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/StringUtils.java
379     */
380    public static String leftPad(final String str, final int size, final char padChar) {
381        if (str == null) {
382            return null;
383        }
384        final var pads = size - str.length();
385        if (pads <= 0) {
386            return str; // returns original String when possible
387        }
388        if (pads > PAD_LIMIT) {
389            return leftPad(str, size, String.valueOf(padChar));
390        }
391        return repeat(padChar, pads).concat(str);
392    }
393
394    /**
395     * <p>
396     * Left pad a String with a specified String.
397     * </p>
398     *
399     * <p>
400     * Pad to a size of {@code size}.
401     * </p>
402     *
403     * <pre>
404     * MoreStrings.leftPad(null, *, *)      = null
405     * MoreStrings.leftPad("", 3, "z")      = "zzz"
406     * MoreStrings.leftPad("bat", 3, "yz")  = "bat"
407     * MoreStrings.leftPad("bat", 5, "yz")  = "yzbat"
408     * MoreStrings.leftPad("bat", 8, "yz")  = "yzyzybat"
409     * MoreStrings.leftPad("bat", 1, "yz")  = "bat"
410     * MoreStrings.leftPad("bat", -1, "yz") = "bat"
411     * MoreStrings.leftPad("bat", 5, null)  = "  bat"
412     * MoreStrings.leftPad("bat", 5, "")    = "  bat"
413     * </pre>
414     *
415     * @param str the String to pad out, may be null
416     * @param size the size to pad to
417     * @param padStr the String to pad with, null or empty treated as single space
418     * @return left padded String or original String if no padding is necessary,
419     *         {@code null} if null String input
420     * @author https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/StringUtils.java
421     */
422    public static String leftPad(final String str, final int size, String padStr) {
423        if (str == null) {
424            return null;
425        }
426        if (isEmpty(padStr)) {
427            padStr = SPACE;
428        }
429        final var padLen = padStr.length();
430        final var strLen = str.length();
431        final var pads = size - strLen;
432        if (pads <= 0) {
433            return str; // returns original String when possible
434        }
435        if (padLen == 1 && pads <= PAD_LIMIT) {
436            return leftPad(str, size, padStr.charAt(0));
437        }
438        if (pads == padLen) {
439            return padStr.concat(str);
440        }
441        if (pads < padLen) {
442            return padStr.substring(0, pads).concat(str);
443        }
444        final var padding = new char[pads];
445        final var padChars = padStr.toCharArray();
446        for (var i = 0; i < pads; i++) {
447            padding[i] = padChars[i % padLen];
448        }
449        return new String(padding).concat(str);
450    }
451
452    /**
453     * <p>
454     * Returns padding using the specified delimiter repeated
455     * to a given length.
456     * </p>
457     *
458     * <pre>
459     * MoreStrings.repeat('e', 0)  = ""
460     * MoreStrings.repeat('e', 3)  = "eee"
461     * MoreStrings.repeat('e', -2) = ""
462     * </pre>
463     *
464     * <p>
465     * Note: this method does not support padding with
466     * <a href="http://www.unicode.org/glossary/#supplementary_character">Unicode Supplementary
467     * Characters</a>
468     * as they require a pair of {@code char}s to be represented.
469     * If you are needing to support full I18N of your applications
470     * consider using <code>repeat(String, int)</code> instead.
471     * </p>
472     *
473     * @param ch character to repeat
474     * @param repeat number of times to repeat char, negative treated as zero
475     * @return String with repeated character
476     * @author https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/StringUtils.java
477     */
478    public static String repeat(final char ch, final int repeat) {
479        if (repeat <= 0) {
480            return EMPTY;
481        }
482        final var buf = new char[repeat];
483        for (var i = repeat - 1; i >= 0; i--) {
484            buf[i] = ch;
485        }
486        return new String(buf);
487    }
488
489    /**
490     * <p>
491     * Strips any of a set of characters from the end of a String.
492     * </p>
493     *
494     * <p>
495     * A {@code null} input String returns {@code null}.
496     * An empty string ("") input returns the empty string.
497     * </p>
498     *
499     * <p>
500     * If the stripChars String is {@code null}, whitespace is
501     * stripped as defined by {@link Character#isWhitespace(char)}.
502     * </p>
503     *
504     * <pre>
505     * MoreStrings.stripEnd(null, *)          = null
506     * MoreStrings.stripEnd("", *)            = ""
507     * MoreStrings.stripEnd("abc", "")        = "abc"
508     * MoreStrings.stripEnd("abc", null)      = "abc"
509     * MoreStrings.stripEnd("  abc", null)    = "  abc"
510     * MoreStrings.stripEnd("abc  ", null)    = "abc"
511     * MoreStrings.stripEnd(" abc ", null)    = " abc"
512     * MoreStrings.stripEnd("  abcyx", "xyz") = "  abc"
513     * MoreStrings.stripEnd("120.00", ".0")   = "12"
514     * </pre>
515     *
516     *
517     * @param str the String to remove characters from, may be null
518     * @param stripChars the set of characters to remove, null treated as whitespace
519     * @return the stripped String, {@code null} if null String input
520     * @author https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/StringUtils.java
521     */
522    public static String stripEnd(final String str, final String stripChars) {
523        int end;
524        if (str == null || (end = str.length()) == 0) {
525            return str;
526        }
527        if (stripChars == null) {
528            while (end != 0 && Character.isWhitespace(str.charAt(end - 1))) {
529                end--;
530            }
531        } else if (stripChars.isEmpty()) {
532            return str;
533        } else {
534            while (end != 0 && stripChars.indexOf(str.charAt(end - 1)) != INDEX_NOT_FOUND) {
535                end--;
536            }
537        }
538        return str.substring(0, end);
539    }
540
541    /**
542     * Returns the index within <code>seq</code> of the first occurrence of
543     * the specified character. If a character with value
544     * <code>searchChar</code> occurs in the character sequence represented by
545     * <code>seq</code> <code>CharSequence</code> object, then the index (in Unicode
546     * code units) of the first such occurrence is returned. For
547     * values of <code>searchChar</code> in the range from 0 to 0xFFFF
548     * (inclusive), this is the smallest value <i>k</i> such that:
549     * <blockquote>
550     *
551     * <pre>
552     * this.charAt(<i>k</i>) == searchChar
553     * </pre>
554     *
555     * </blockquote>
556     * is true. For other values of <code>searchChar</code>, it is the
557     * smallest value <i>k</i> such that:
558     * <blockquote>
559     *
560     * <pre>
561     * this.codePointAt(<i>k</i>) == searchChar
562     * </pre>
563     *
564     * </blockquote>
565     * is true. In either case, if no such character occurs in <code>seq</code>,
566     * then {@code INDEX_NOT_FOUND (-1)} is returned.
567     *
568     * <p>
569     * Furthermore, a {@code null} or empty ("") CharSequence will
570     * return {@code INDEX_NOT_FOUND (-1)}.
571     * </p>
572     *
573     * <pre>
574     * MoreStrings.indexOf(null, *)         = -1
575     * MoreStrings.indexOf("", *)           = -1
576     * MoreStrings.indexOf("aabaabaa", 'a') = 0
577     * MoreStrings.indexOf("aabaabaa", 'b') = 2
578     * </pre>
579     *
580     *
581     * @param seq the CharSequence to check, may be null
582     * @param searchChar the character to find
583     * @return the first index of the search character,
584     *         -1 if no match or {@code null} string input
585     * @author https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/StringUtils.java
586     */
587    public static int indexOf(final CharSequence seq, final int searchChar) {
588        if (isEmpty(seq)) {
589            return INDEX_NOT_FOUND;
590        }
591        return indexOf(seq, searchChar, 0);
592    }
593
594    // owolff: original code
595    /**
596     * Returns the index within <code>cs</code> of the first occurrence of the
597     * specified character, starting the search at the specified index.
598     * <p>
599     * If a character with value <code>searchChar</code> occurs in the
600     * character sequence represented by the <code>cs</code>
601     * object at an index no smaller than <code>start</code>, then
602     * the index of the first such occurrence is returned. For values
603     * of <code>searchChar</code> in the range from 0 to 0xFFFF (inclusive),
604     * this is the smallest value <i>k</i> such that:
605     * <blockquote>
606     *
607     * <pre>
608     * (this.charAt(<i>k</i>) == searchChar) &amp;&amp; (<i>k</i> &gt;= start)
609     * </pre>
610     *
611     * </blockquote>
612     * is true. For other values of <code>searchChar</code>, it is the
613     * smallest value <i>k</i> such that:
614     * <blockquote>
615     *
616     * <pre>
617     * (this.codePointAt(<i>k</i>) == searchChar) &amp;&amp; (<i>k</i> &gt;= start)
618     * </pre>
619     *
620     * </blockquote>
621     * is true. In either case, if no such character occurs inm <code>cs</code>
622     * at or after position <code>start</code>, then
623     * <code>-1</code> is returned.
624     *
625     * <p>
626     * There is no restriction on the value of <code>start</code>. If it
627     * is negative, it has the same effect as if it were zero: the entire
628     * <code>CharSequence</code> may be searched. If it is greater than
629     * the length of <code>cs</code>, it has the same effect as if it were
630     * equal to the length of <code>cs</code>: <code>-1</code> is returned.
631     *
632     * <p>
633     * All indices are specified in <code>char</code> values
634     * (Unicode code units).
635     *
636     * @param cs the {@code CharSequence} to be processed, not null
637     * @param searchChar the char to be searched for
638     * @param start the start index, negative starts at the string start
639     * @return the index where the search char was found, -1 if not found
640     * @author https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/StringUtils.java
641     */
642    @SuppressWarnings("squid:S3776")
643    public static int indexOf(final CharSequence cs, final int searchChar, int start) {
644        if (isEmpty(cs)) {
645            return INDEX_NOT_FOUND;
646        }
647        if (cs instanceof String) {
648            return ((String) cs).indexOf(searchChar, start);
649        }
650        final var sz = cs.length();
651        if (start < 0) {
652            start = 0;
653        }
654        if (searchChar < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
655            for (var i = start; i < sz; i++) {
656                if (cs.charAt(i) == searchChar) {
657                    return i;
658                }
659            }
660        }
661        // supplementary characters (LANG1300)
662        if (searchChar <= Character.MAX_CODE_POINT) {
663            final var chars = Character.toChars(searchChar);
664            for (var i = start; i < sz - 1; i++) {
665                final var high = cs.charAt(i);
666                final var low = cs.charAt(i + 1);
667                if (high == chars[0] && low == chars[1]) {
668                    return i;
669                }
670            }
671        }
672        return INDEX_NOT_FOUND;
673    }
674
675    /**
676     * Used by the indexOf(CharSequence methods) as a green implementation of indexOf.
677     *
678     * @param cs the {@code CharSequence} to be processed
679     * @param searchChar the {@code CharSequence} to be searched for
680     * @param start the start index
681     * @return the index where the search sequence was found
682     *
683     * @author https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/StringUtils.java
684     */
685    public static int indexOf(final CharSequence cs, final CharSequence searchChar, final int start) {
686        if (cs == null || searchChar == null) {
687            return INDEX_NOT_FOUND;
688        }
689        return cs.toString().indexOf(searchChar.toString(), start);
690    }
691
692    /**
693     * Check whether the given {@code String} contains actual <em>text</em>.
694     * <p>
695     * More specifically, this method returns {@code true} if the
696     * {@code String} is not {@code null}, its length is greater than 0,
697     * and it contains at least one non-whitespace character.
698     *
699     * @param str the {@code String} to check (maybe {@code null})
700     *
701     * @return {@code true} if the {@code String} is not {@code null}, its
702     *         length is greater than 0, and it does not contain whitespace only
703     * @author https://github.com/spring-projects/spring-framework/blob/v5.1.8.RELEASE/spring-core/src/main/java/org/springframework/util/StringUtils.java
704     */
705    public static boolean hasNonWhitespaceChar(final CharSequence str) {
706        if (isEmpty(str)) {
707            return false;
708        }
709        final var strLen = str.length();
710        for (var i = 0; i < strLen; i++) {
711            if (!Character.isWhitespace(str.charAt(i))) {
712                return true;
713            }
714        }
715        return false;
716    }
717
718    /**
719     * Checks a string for being non-null and not empty (checks without trimming) Throws an
720     * {@link IllegalArgumentException} if String is null or empty
721     *
722     * @param underCheck
723     * @return the given String
724     */
725    public static String requireNotEmpty(final String underCheck) {
726        checkArgument(!isEmpty(underCheck), "Given String is Empty");
727        return underCheck;
728    }
729
730    /**
731     * Checks a string for being non-null and not empty (checks without trimming) Throws an
732     * {@link IllegalArgumentException} if String is null or empty
733     *
734     * @param underCheck
735     * @param attributeName used for the creation of the error text
736     * @return the given String
737     */
738    public static String requireNotEmpty(final String underCheck, final String attributeName) {
739        checkArgument(!isEmpty(underCheck), "Attribute with name \'" + attributeName + "\' must not be empty");
740        return underCheck;
741    }
742
743    /**
744     * Checks a string for being non-null and not empty (checks with trimming) Throws an
745     * {@link IllegalArgumentException} if String is null or empty
746     *
747     * @param underCheck
748     * @return the given String
749     */
750    public static String requireNotEmptyTrimmed(final String underCheck) {
751        checkArgument(!isBlank(underCheck), "Attribute must not be blank");
752        return underCheck;
753    }
754
755    /**
756     * Checks a string for being non-null and not empty (checks with trimming) Throws an
757     * {@link IllegalArgumentException} if String is null or empty
758     *
759     * @param underCheck
760     * @param attributeName used for the creation of the error text
761     * @return the given String
762     */
763    public static String requireNotEmptyTrimmed(final String underCheck, final String attributeName) {
764        checkArgument(!isBlank(underCheck), "Attribute with name \'" + attributeName + "\' must not be blank");
765        return underCheck;
766    }
767
768    /**
769     * Returns the given string if it is non-null; the empty string otherwise.
770     *
771     * @author com.google.common.base.Strings
772     * @param string the string to test and possibly return
773     * @return {@code string} itself if it is non-null; {@code ""} if it is null
774     */
775    public static String nullToEmpty(String string) {
776        if (null == string) {
777            return "";
778        }
779        return string;
780    }
781
782    /**
783     * Returns the given string if it is nonempty; {@code null} otherwise.
784     *
785     * @author com.google.common.base.Strings
786     * @param string the string to test and possibly return
787     * @return {@code string} itself if it is nonempty; {@code null} if it is empty or null
788     */
789    public static String emptyToNull(String string) {
790        if (null == string || string.isEmpty()) {
791            return null;
792        }
793        return string;
794    }
795
796    /**
797     * Null-safe trimming of a String value.
798     *
799     * @param string to be trimmed
800     *
801     * @return <code>null</code> if the string is <code>null</code> otherwise the trimmed string
802     */
803    public static String trimOrNull(final String string) {
804        return null != string ? string.trim() : null;
805    }
806
807    /**
808     * Returns the given {@code template} string with each occurrence of {@code "%s"} replaced with
809     * the corresponding argument value from {@code args}; or, if the placeholder and argument
810     * counts
811     * do not match, returns a best-effort form of that string. Will not throw an exception under
812     * normal conditions.
813     *
814     * <p>
815     * <b>Note:</b> For most string-formatting needs, use {@link String#format String.format},
816     * {@link java.io.PrintWriter#format PrintWriter.format}, and related methods. These support the
817     * full range of <a
818     * href="https://docs.oracle.com/javase/9/docs/api/java/util/Formatter.html#syntax">format
819     * specifiers</a>, and alert you to usage errors by throwing {@link
820     * java.util.IllegalFormatException}.
821     *
822     * <p>
823     * In certain cases, such as outputting debugging information or constructing a message to be
824     * used for another unchecked exception, an exception during string formatting would serve
825     * little purpose except to supplant the real information you were trying to provide. These are
826     * the cases this method is made for; it instead generates a best-effort string with all
827     * supplied argument values present. This method is also useful in environments such as GWT
828     * where {@code String.format} is not available.
829     *
830     * <p>
831     * <b>Warning:</b> Only the exact two-character placeholder sequence {@code "%s"} is
832     * recognized.
833     *
834     * @author com.google.common.base.Strings
835     * @param template a string containing zero or more {@code "%s"} placeholder sequences. {@code
836     *     null} is treated as the four-character string {@code "null"}.
837     * @param args the arguments to be substituted into the message template. The first argument
838     *            specified is substituted for the first occurrence of {@code "%s"} in the template,
839     *            and so forth. A {@code null} argument is converted to the four-character string
840     *            {@code "null"}; non-null values are converted to strings using
841     *            {@link Object#toString()}.
842     * @return the resulting formatting String
843     */
844    public static String lenientFormat(String template, Object... args) {
845        template = String.valueOf(template); // null -> "null"
846        if (args == null) {
847            args = new Object[] {"(Object[])null"};
848        } else {
849            for (var i = 0; i < args.length; i++) {
850                args[i] = lenientToString(args[i]);
851            }
852        }
853        // start substituting the arguments into the '%s' placeholders
854        var builder = new StringBuilder(template.length() + 16 * args.length);
855        var templateStart = 0;
856        var i = 0;
857        while (i < args.length) {
858            var placeholderStart = template.indexOf("%s", templateStart);
859            if (placeholderStart == -1) {
860                break;
861            }
862            builder.append(template, templateStart, placeholderStart);
863            builder.append(args[i]);
864            i++;
865            templateStart = placeholderStart + 2;
866        }
867        builder.append(template, templateStart, template.length());
868        // if we run out of placeholders, append the extra args in square braces
869        if (i < args.length) {
870            builder.append(" [");
871            builder.append(args[i]);
872            i++;
873            while (i < args.length) {
874                builder.append(", ");
875                builder.append(args[i]);
876                i++;
877            }
878            builder.append(']');
879        }
880        return builder.toString();
881    }
882
883    /**
884     * @param value to be processed
885     * @param suffix to be present
886     * @return value with suffix
887     */
888    public static String ensureEndsWith(@NonNull final String value, @NonNull final String suffix) {
889        if (value == null) {
890            throw new java.lang.NullPointerException("value is marked non-null but is null");
891        }
892        if (suffix == null) {
893            throw new java.lang.NullPointerException("suffix is marked non-null but is null");
894        }
895        if (!value.endsWith(suffix)) {
896            return value + suffix;
897        }
898        return value;
899    }
900
901    /**
902     * @author com.google.common.base.Strings
903     */
904    static String lenientToString(Object o) {
905        try {
906            return String.valueOf(o);
907        } catch (Exception e) {
908            // Default toString() behavior - see Object.toString()
909            var objectToString = o.getClass().getName() + '@' + Integer.toHexString(System.identityHashCode(o));
910            log.warn(e, "Exception during lenientFormat for {}", objectToString);
911            return "<" + objectToString + " threw " + e.getClass().getName() + ">";
912        }
913    }
914
915    /**
916     * @param checker the predicate to check each given value against. it decides if a value
917     *            qualifies to be returned.
918     * @param values to be evaluated
919     * @return first string that is accepted by the given {@link Predicate} or
920     *         {@link Optional#empty()}
921     */
922    public static Optional<String> coalesce(Predicate<String> checker, String... values) {
923        if (null != values) {
924            for (String value : values) {
925                if (!checker.test(value)) {
926                    return Optional.of(value);
927                }
928            }
929        }
930        return Optional.empty();
931    }
932
933    /**
934     * @param values to be evaluated
935     * @return first string that is not {@link #isEmpty(CharSequence)}
936     */
937    public static Optional<String> firstNonEmpty(String... values) {
938        return coalesce(MoreStrings::isEmpty, values);
939    }
940
941    /**
942     * @param values to be evaluated
943     * @return first string that is not {@link #isBlank(CharSequence)}
944     */
945    public static Optional<String> firstNonBlank(String... values) {
946        return coalesce(MoreStrings::isBlank, values);
947    }
948
949    @java.lang.SuppressWarnings("all")
950    @lombok.Generated
951    private MoreStrings() {
952        throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated");
953    }
954}