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