001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.camel.util; 018 019import java.util.ArrayList; 020import java.util.Collections; 021import java.util.Iterator; 022import java.util.List; 023import java.util.Locale; 024import java.util.NoSuchElementException; 025import java.util.Objects; 026import java.util.Optional; 027import java.util.function.Function; 028import java.util.regex.Matcher; 029import java.util.regex.Pattern; 030import java.util.stream.Stream; 031 032/** 033 * Helper methods for working with Strings. 034 */ 035public final class StringHelper { 036 037 /** 038 * Constructor of utility class should be private. 039 */ 040 private StringHelper() { 041 } 042 043 /** 044 * Ensures that <code>s</code> is friendly for a URL or file system. 045 * 046 * @param s String to be sanitized. 047 * @return sanitized version of <code>s</code>. 048 * @throws NullPointerException if <code>s</code> is <code>null</code>. 049 */ 050 public static String sanitize(String s) { 051 return s.replace(':', '-') 052 .replace('_', '-') 053 .replace('.', '-') 054 .replace('/', '-') 055 .replace('\\', '-'); 056 } 057 058 /** 059 * Remove carriage return and line feeds from a String, replacing them with an empty String. 060 * 061 * @param s String to be sanitized of carriage return / line feed characters 062 * @return sanitized version of <code>s</code>. 063 * @throws NullPointerException if <code>s</code> is <code>null</code>. 064 */ 065 public static String removeCRLF(String s) { 066 return s 067 .replace("\r", "") 068 .replace("\n", ""); 069 } 070 071 /** 072 * Counts the number of times the given char is in the string 073 * 074 * @param s the string 075 * @param ch the char 076 * @return number of times char is located in the string 077 */ 078 public static int countChar(String s, char ch) { 079 return countChar(s, ch, -1); 080 } 081 082 /** 083 * Counts the number of times the given char is in the string 084 * 085 * @param s the string 086 * @param ch the char 087 * @param end end index 088 * @return number of times char is located in the string 089 */ 090 public static int countChar(String s, char ch, int end) { 091 if (s == null || s.isEmpty()) { 092 return 0; 093 } 094 095 int matches = 0; 096 int len = end < 0 ? s.length() : end; 097 for (int i = 0; i < len; i++) { 098 char c = s.charAt(i); 099 if (ch == c) { 100 matches++; 101 } 102 } 103 104 return matches; 105 } 106 107 /** 108 * Limits the length of a string 109 * 110 * @param s the string 111 * @param maxLength the maximum length of the returned string 112 * @return s if the length of s is less than maxLength or the first maxLength characters of s 113 */ 114 public static String limitLength(String s, int maxLength) { 115 if (ObjectHelper.isEmpty(s)) { 116 return s; 117 } 118 return s.length() <= maxLength ? s : s.substring(0, maxLength); 119 } 120 121 /** 122 * Removes all quotes (single and double) from the string 123 * 124 * @param s the string 125 * @return the string without quotes (single and double) 126 */ 127 public static String removeQuotes(String s) { 128 if (ObjectHelper.isEmpty(s)) { 129 return s; 130 } 131 132 s = s.replace("'", ""); 133 s = s.replace("\"", ""); 134 return s; 135 } 136 137 /** 138 * Removes all leading and ending quotes (single and double) from the string 139 * 140 * @param s the string 141 * @return the string without leading and ending quotes (single and double) 142 */ 143 public static String removeLeadingAndEndingQuotes(String s) { 144 if (ObjectHelper.isEmpty(s)) { 145 return s; 146 } 147 148 String copy = s.trim(); 149 if (copy.length() < 2) { 150 return s; 151 } 152 if (copy.startsWith("'") && copy.endsWith("'")) { 153 return copy.substring(1, copy.length() - 1); 154 } 155 if (copy.startsWith("\"") && copy.endsWith("\"")) { 156 return copy.substring(1, copy.length() - 1); 157 } 158 159 // no quotes, so return as-is 160 return s; 161 } 162 163 /** 164 * Whether the string starts and ends with either single or double quotes. 165 * 166 * @param s the string 167 * @return <tt>true</tt> if the string starts and ends with either single or double quotes. 168 */ 169 public static boolean isQuoted(String s) { 170 return isSingleQuoted(s) || isDoubleQuoted(s); 171 } 172 173 /** 174 * Whether the string starts and ends with single quotes. 175 * 176 * @param s the string 177 * @return <tt>true</tt> if the string starts and ends with single quotes. 178 */ 179 public static boolean isSingleQuoted(String s) { 180 if (ObjectHelper.isEmpty(s)) { 181 return false; 182 } 183 184 if (s.startsWith("'") && s.endsWith("'")) { 185 return true; 186 } 187 188 return false; 189 } 190 191 /** 192 * Whether the string starts and ends with double quotes. 193 * 194 * @param s the string 195 * @return <tt>true</tt> if the string starts and ends with double quotes. 196 */ 197 public static boolean isDoubleQuoted(String s) { 198 if (ObjectHelper.isEmpty(s)) { 199 return false; 200 } 201 202 if (s.startsWith("\"") && s.endsWith("\"")) { 203 return true; 204 } 205 206 return false; 207 } 208 209 /** 210 * Encodes the text into safe XML by replacing < > and & with XML tokens 211 * 212 * @param text the text 213 * @return the encoded text 214 */ 215 public static String xmlEncode(String text) { 216 if (text == null) { 217 return ""; 218 } 219 // must replace amp first, so we dont replace < to amp later 220 text = text.replace("&", "&"); 221 text = text.replace("\"", """); 222 text = text.replace("<", "<"); 223 text = text.replace(">", ">"); 224 return text; 225 } 226 227 /** 228 * Determines if the string has at least one letter in upper case 229 * 230 * @param text the text 231 * @return <tt>true</tt> if at least one letter is upper case, <tt>false</tt> otherwise 232 */ 233 public static boolean hasUpperCase(String text) { 234 if (text == null) { 235 return false; 236 } 237 238 for (int i = 0; i < text.length(); i++) { 239 char ch = text.charAt(i); 240 if (Character.isUpperCase(ch)) { 241 return true; 242 } 243 } 244 245 return false; 246 } 247 248 /** 249 * Determines if the string is a fully qualified class name 250 */ 251 public static boolean isClassName(String text) { 252 boolean result = false; 253 if (text != null) { 254 String[] split = text.split("\\."); 255 if (split.length > 0) { 256 String lastToken = split[split.length - 1]; 257 if (lastToken.length() > 0) { 258 result = Character.isUpperCase(lastToken.charAt(0)); 259 } 260 } 261 } 262 return result; 263 } 264 265 /** 266 * Does the expression have the language start token? 267 * 268 * @param expression the expression 269 * @param language the name of the language, such as simple 270 * @return <tt>true</tt> if the expression contains the start token, <tt>false</tt> otherwise 271 */ 272 public static boolean hasStartToken(String expression, String language) { 273 if (expression == null) { 274 return false; 275 } 276 277 // for the simple language the expression start token could be "${" 278 if ("simple".equalsIgnoreCase(language) && expression.contains("${")) { 279 return true; 280 } 281 282 if (language != null && expression.contains("$" + language + "{")) { 283 return true; 284 } 285 286 return false; 287 } 288 289 /** 290 * Replaces the first from token in the given input string. 291 * <p/> 292 * This implementation is not recursive, not does it check for tokens in the replacement string. 293 * 294 * @param input the input string 295 * @param from the from string, must <b>not</b> be <tt>null</tt> or empty 296 * @param to the replacement string, must <b>not</b> be empty 297 * @return the replaced string, or the input string if no replacement was needed 298 * @throws IllegalArgumentException if the input arguments is invalid 299 */ 300 public static String replaceFirst(String input, String from, String to) { 301 int pos = input.indexOf(from); 302 if (pos != -1) { 303 int len = from.length(); 304 return input.substring(0, pos) + to + input.substring(pos + len); 305 } else { 306 return input; 307 } 308 } 309 310 /** 311 * Creates a json tuple with the given name/value pair. 312 * 313 * @param name the name 314 * @param value the value 315 * @param isMap whether the tuple should be map 316 * @return the json 317 */ 318 public static String toJson(String name, String value, boolean isMap) { 319 if (isMap) { 320 return "{ " + StringQuoteHelper.doubleQuote(name) + ": " + StringQuoteHelper.doubleQuote(value) + " }"; 321 } else { 322 return StringQuoteHelper.doubleQuote(name) + ": " + StringQuoteHelper.doubleQuote(value); 323 } 324 } 325 326 /** 327 * Asserts whether the string is <b>not</b> empty. 328 * 329 * @param value the string to test 330 * @param name the key that resolved the value 331 * @return the passed {@code value} as is 332 * @throws IllegalArgumentException is thrown if assertion fails 333 */ 334 public static String notEmpty(String value, String name) { 335 if (ObjectHelper.isEmpty(value)) { 336 throw new IllegalArgumentException(name + " must be specified and not empty"); 337 } 338 339 return value; 340 } 341 342 /** 343 * Asserts whether the string is <b>not</b> empty. 344 * 345 * @param value the string to test 346 * @param on additional description to indicate where this problem occurred (appended as 347 * toString()) 348 * @param name the key that resolved the value 349 * @return the passed {@code value} as is 350 * @throws IllegalArgumentException is thrown if assertion fails 351 */ 352 public static String notEmpty(String value, String name, Object on) { 353 if (on == null) { 354 ObjectHelper.notNull(value, name); 355 } else if (ObjectHelper.isEmpty(value)) { 356 throw new IllegalArgumentException(name + " must be specified and not empty on: " + on); 357 } 358 359 return value; 360 } 361 362 public static String[] splitOnCharacter(String value, String needle, int count) { 363 String[] rc = new String[count]; 364 rc[0] = value; 365 for (int i = 1; i < count; i++) { 366 String v = rc[i - 1]; 367 int p = v.indexOf(needle); 368 if (p < 0) { 369 return rc; 370 } 371 rc[i - 1] = v.substring(0, p); 372 rc[i] = v.substring(p + 1); 373 } 374 return rc; 375 } 376 377 public static Iterator<String> splitOnCharacterAsIterator(String value, char needle, int count) { 378 // skip leading and trailing needles 379 int end = value.length() - 1; 380 boolean skipStart = value.charAt(0) == needle; 381 boolean skipEnd = value.charAt(end) == needle; 382 if (skipStart && skipEnd) { 383 value = value.substring(1, end); 384 count = count - 2; 385 } else if (skipStart) { 386 value = value.substring(1); 387 count = count - 1; 388 } else if (skipEnd) { 389 value = value.substring(0, end); 390 count = count - 1; 391 } 392 393 final int size = count; 394 final String text = value; 395 396 return new Iterator<String>() { 397 int i; 398 int pos; 399 400 @Override 401 public boolean hasNext() { 402 return i < size; 403 } 404 405 @Override 406 public String next() { 407 if (i == size) { 408 throw new NoSuchElementException(); 409 } 410 String answer; 411 int end = text.indexOf(needle, pos); 412 if (end != -1) { 413 answer = text.substring(pos, end); 414 pos = end + 1; 415 } else { 416 answer = text.substring(pos); 417 // no more data 418 i = size; 419 } 420 return answer; 421 } 422 }; 423 } 424 425 public static List<String> splitOnCharacterAsList(String value, char needle, int count) { 426 // skip leading and trailing needles 427 int end = value.length() - 1; 428 boolean skipStart = value.charAt(0) == needle; 429 boolean skipEnd = value.charAt(end) == needle; 430 if (skipStart && skipEnd) { 431 value = value.substring(1, end); 432 count = count - 2; 433 } else if (skipStart) { 434 value = value.substring(1); 435 count = count - 1; 436 } else if (skipEnd) { 437 value = value.substring(0, end); 438 count = count - 1; 439 } 440 441 List<String> rc = new ArrayList<>(count); 442 int pos = 0; 443 for (int i = 0; i < count; i++) { 444 end = value.indexOf(needle, pos); 445 if (end != -1) { 446 String part = value.substring(pos, end); 447 pos = end + 1; 448 rc.add(part); 449 } else { 450 rc.add(value.substring(pos)); 451 break; 452 } 453 } 454 return rc; 455 } 456 457 /** 458 * Removes any starting characters on the given text which match the given character 459 * 460 * @param text the string 461 * @param ch the initial characters to remove 462 * @return either the original string or the new substring 463 */ 464 public static String removeStartingCharacters(String text, char ch) { 465 int idx = 0; 466 while (text.charAt(idx) == ch) { 467 idx++; 468 } 469 if (idx > 0) { 470 return text.substring(idx); 471 } 472 return text; 473 } 474 475 /** 476 * Capitalize the string (upper case first character) 477 * 478 * @param text the string 479 * @return the string capitalized (upper case first character) 480 */ 481 public static String capitalize(String text) { 482 return capitalize(text, false); 483 } 484 485 /** 486 * Capitalize the string (upper case first character) 487 * 488 * @param text the string 489 * @param dashToCamelCase whether to also convert dash format into camel case (hello-great-world -> 490 * helloGreatWorld) 491 * @return the string capitalized (upper case first character) 492 */ 493 public static String capitalize(String text, boolean dashToCamelCase) { 494 if (dashToCamelCase) { 495 text = dashToCamelCase(text); 496 } 497 if (text == null) { 498 return null; 499 } 500 int length = text.length(); 501 if (length == 0) { 502 return text; 503 } 504 String answer = text.substring(0, 1).toUpperCase(Locale.ENGLISH); 505 if (length > 1) { 506 answer += text.substring(1, length); 507 } 508 return answer; 509 } 510 511 /** 512 * Converts the string from dash format into camel case (hello-great-world -> helloGreatWorld) 513 * 514 * @param text the string 515 * @return the string camel cased 516 */ 517 public static String dashToCamelCase(String text) { 518 if (text == null) { 519 return null; 520 } 521 int length = text.length(); 522 if (length == 0) { 523 return text; 524 } 525 if (text.indexOf('-') == -1) { 526 return text; 527 } 528 529 // there is at least 1 dash so the capacity can be shorter 530 StringBuilder sb = new StringBuilder(length - 1); 531 boolean upper = false; 532 for (int i = 0; i < length; i++) { 533 char c = text.charAt(i); 534 if (c == '-') { 535 upper = true; 536 } else { 537 if (upper) { 538 c = Character.toUpperCase(c); 539 } 540 sb.append(c); 541 upper = false; 542 } 543 } 544 return sb.toString(); 545 } 546 547 /** 548 * Returns the string after the given token 549 * 550 * @param text the text 551 * @param after the token 552 * @return the text after the token, or <tt>null</tt> if text does not contain the token 553 */ 554 public static String after(String text, String after) { 555 int pos = text.indexOf(after); 556 if (pos == -1) { 557 return null; 558 } 559 return text.substring(pos + after.length()); 560 } 561 562 /** 563 * Returns the string after the given token, or the default value 564 * 565 * @param text the text 566 * @param after the token 567 * @param defaultValue the value to return if text does not contain the token 568 * @return the text after the token, or the supplied defaultValue if text does not contain the token 569 */ 570 public static String after(String text, String after, String defaultValue) { 571 String answer = after(text, after); 572 return answer != null ? answer : defaultValue; 573 } 574 575 /** 576 * Returns an object after the given token 577 * 578 * @param text the text 579 * @param after the token 580 * @param mapper a mapping function to convert the string after the token to type T 581 * @return an Optional describing the result of applying a mapping function to the text after the token. 582 */ 583 public static <T> Optional<T> after(String text, String after, Function<String, T> mapper) { 584 String result = after(text, after); 585 if (result == null) { 586 return Optional.empty(); 587 } else { 588 return Optional.ofNullable(mapper.apply(result)); 589 } 590 } 591 592 /** 593 * Returns the string after the the last occurrence of the given token 594 * 595 * @param text the text 596 * @param after the token 597 * @return the text after the token, or <tt>null</tt> if text does not contain the token 598 */ 599 public static String afterLast(String text, String after) { 600 int pos = text.lastIndexOf(after); 601 if (pos == -1) { 602 return null; 603 } 604 return text.substring(pos + after.length()); 605 } 606 607 /** 608 * Returns the string after the the last occurrence of the given token, or the default value 609 * 610 * @param text the text 611 * @param after the token 612 * @param defaultValue the value to return if text does not contain the token 613 * @return the text after the token, or the supplied defaultValue if text does not contain the token 614 */ 615 public static String afterLast(String text, String after, String defaultValue) { 616 String answer = afterLast(text, after); 617 return answer != null ? answer : defaultValue; 618 } 619 620 /** 621 * Returns the string before the given token 622 * 623 * @param text the text 624 * @param before the token 625 * @return the text before the token, or <tt>null</tt> if text does not contain the token 626 */ 627 public static String before(String text, String before) { 628 int pos = text.indexOf(before); 629 return pos == -1 ? null : text.substring(0, pos); 630 } 631 632 /** 633 * Returns the string before the given token, or the default value 634 * 635 * @param text the text 636 * @param before the token 637 * @param defaultValue the value to return if text does not contain the token 638 * @return the text before the token, or the supplied defaultValue if text does not contain the token 639 */ 640 public static String before(String text, String before, String defaultValue) { 641 String answer = before(text, before); 642 return answer != null ? answer : defaultValue; 643 } 644 645 /** 646 * Returns an object before the given token 647 * 648 * @param text the text 649 * @param before the token 650 * @param mapper a mapping function to convert the string before the token to type T 651 * @return an Optional describing the result of applying a mapping function to the text before the token. 652 */ 653 public static <T> Optional<T> before(String text, String before, Function<String, T> mapper) { 654 String result = before(text, before); 655 if (result == null) { 656 return Optional.empty(); 657 } else { 658 return Optional.ofNullable(mapper.apply(result)); 659 } 660 } 661 662 /** 663 * Returns the string before the last occurrence of the given token 664 * 665 * @param text the text 666 * @param before the token 667 * @return the text before the token, or <tt>null</tt> if text does not contain the token 668 */ 669 public static String beforeLast(String text, String before) { 670 int pos = text.lastIndexOf(before); 671 return pos == -1 ? null : text.substring(0, pos); 672 } 673 674 /** 675 * Returns the string before the last occurrence of the given token, or the default value 676 * 677 * @param text the text 678 * @param before the token 679 * @param defaultValue the value to return if text does not contain the token 680 * @return the text before the token, or the supplied defaultValue if text does not contain the token 681 */ 682 public static String beforeLast(String text, String before, String defaultValue) { 683 String answer = beforeLast(text, before); 684 return answer != null ? answer : defaultValue; 685 } 686 687 /** 688 * Returns the string between the given tokens 689 * 690 * @param text the text 691 * @param after the before token 692 * @param before the after token 693 * @return the text between the tokens, or <tt>null</tt> if text does not contain the tokens 694 */ 695 public static String between(String text, String after, String before) { 696 text = after(text, after); 697 if (text == null) { 698 return null; 699 } 700 return before(text, before); 701 } 702 703 /** 704 * Returns an object between the given token 705 * 706 * @param text the text 707 * @param after the before token 708 * @param before the after token 709 * @param mapper a mapping function to convert the string between the token to type T 710 * @return an Optional describing the result of applying a mapping function to the text between the token. 711 */ 712 public static <T> Optional<T> between(String text, String after, String before, Function<String, T> mapper) { 713 String result = between(text, after, before); 714 if (result == null) { 715 return Optional.empty(); 716 } else { 717 return Optional.ofNullable(mapper.apply(result)); 718 } 719 } 720 721 /** 722 * Returns the string between the most outer pair of tokens 723 * <p/> 724 * The number of token pairs must be evenly, eg there must be same number of before and after tokens, otherwise 725 * <tt>null</tt> is returned 726 * <p/> 727 * This implementation skips matching when the text is either single or double quoted. For example: 728 * <tt>${body.matches("foo('bar')")</tt> Will not match the parenthesis from the quoted text. 729 * 730 * @param text the text 731 * @param after the before token 732 * @param before the after token 733 * @return the text between the outer most tokens, or <tt>null</tt> if text does not contain the tokens 734 */ 735 public static String betweenOuterPair(String text, char before, char after) { 736 if (text == null) { 737 return null; 738 } 739 740 int pos = -1; 741 int pos2 = -1; 742 int count = 0; 743 int count2 = 0; 744 745 boolean singleQuoted = false; 746 boolean doubleQuoted = false; 747 for (int i = 0; i < text.length(); i++) { 748 char ch = text.charAt(i); 749 if (!doubleQuoted && ch == '\'') { 750 singleQuoted = !singleQuoted; 751 } else if (!singleQuoted && ch == '\"') { 752 doubleQuoted = !doubleQuoted; 753 } 754 if (singleQuoted || doubleQuoted) { 755 continue; 756 } 757 758 if (ch == before) { 759 count++; 760 } else if (ch == after) { 761 count2++; 762 } 763 764 if (ch == before && pos == -1) { 765 pos = i; 766 } else if (ch == after) { 767 pos2 = i; 768 } 769 } 770 771 if (pos == -1 || pos2 == -1) { 772 return null; 773 } 774 775 // must be even paris 776 if (count != count2) { 777 return null; 778 } 779 780 return text.substring(pos + 1, pos2); 781 } 782 783 /** 784 * Returns an object between the most outer pair of tokens 785 * 786 * @param text the text 787 * @param after the before token 788 * @param before the after token 789 * @param mapper a mapping function to convert the string between the most outer pair of tokens to type T 790 * @return an Optional describing the result of applying a mapping function to the text between the most 791 * outer pair of tokens. 792 */ 793 public static <T> Optional<T> betweenOuterPair(String text, char before, char after, Function<String, T> mapper) { 794 String result = betweenOuterPair(text, before, after); 795 if (result == null) { 796 return Optional.empty(); 797 } else { 798 return Optional.ofNullable(mapper.apply(result)); 799 } 800 } 801 802 /** 803 * Returns true if the given name is a valid java identifier 804 */ 805 public static boolean isJavaIdentifier(String name) { 806 if (name == null) { 807 return false; 808 } 809 int size = name.length(); 810 if (size < 1) { 811 return false; 812 } 813 if (Character.isJavaIdentifierStart(name.charAt(0))) { 814 for (int i = 1; i < size; i++) { 815 if (!Character.isJavaIdentifierPart(name.charAt(i))) { 816 return false; 817 } 818 } 819 return true; 820 } 821 return false; 822 } 823 824 /** 825 * Cleans the string to a pure Java identifier so we can use it for loading class names. 826 * <p/> 827 * Especially from Spring DSL people can have \n \t or other characters that otherwise would result in 828 * ClassNotFoundException 829 * 830 * @param name the class name 831 * @return normalized classname that can be load by a class loader. 832 */ 833 public static String normalizeClassName(String name) { 834 StringBuilder sb = new StringBuilder(name.length()); 835 for (char ch : name.toCharArray()) { 836 if (ch == '.' || ch == '[' || ch == ']' || ch == '-' || Character.isJavaIdentifierPart(ch)) { 837 sb.append(ch); 838 } 839 } 840 return sb.toString(); 841 } 842 843 /** 844 * Compares old and new text content and report back which lines are changed 845 * 846 * @param oldText the old text 847 * @param newText the new text 848 * @return a list of line numbers that are changed in the new text 849 */ 850 public static List<Integer> changedLines(String oldText, String newText) { 851 if (oldText == null || oldText.equals(newText)) { 852 return Collections.emptyList(); 853 } 854 855 List<Integer> changed = new ArrayList<>(); 856 857 String[] oldLines = oldText.split("\n"); 858 String[] newLines = newText.split("\n"); 859 860 for (int i = 0; i < newLines.length; i++) { 861 String newLine = newLines[i]; 862 String oldLine = i < oldLines.length ? oldLines[i] : null; 863 if (oldLine == null) { 864 changed.add(i); 865 } else if (!newLine.equals(oldLine)) { 866 changed.add(i); 867 } 868 } 869 870 return changed; 871 } 872 873 /** 874 * Removes the leading and trailing whitespace and if the resulting string is empty returns {@code null}. Examples: 875 * <p> 876 * Examples: <blockquote> 877 * 878 * <pre> 879 * trimToNull("abc") -> "abc" 880 * trimToNull(" abc") -> "abc" 881 * trimToNull(" abc ") -> "abc" 882 * trimToNull(" ") -> null 883 * trimToNull("") -> null 884 * </pre> 885 * 886 * </blockquote> 887 */ 888 public static String trimToNull(final String given) { 889 if (given == null) { 890 return null; 891 } 892 893 final String trimmed = given.trim(); 894 895 if (trimmed.isEmpty()) { 896 return null; 897 } 898 899 return trimmed; 900 } 901 902 /** 903 * Checks if the src string contains what 904 * 905 * @param src is the source string to be checked 906 * @param what is the string which will be looked up in the src argument 907 * @return true/false 908 */ 909 public static boolean containsIgnoreCase(String src, String what) { 910 if (src == null || what == null) { 911 return false; 912 } 913 914 final int length = what.length(); 915 if (length == 0) { 916 return true; // Empty string is contained 917 } 918 919 final char firstLo = Character.toLowerCase(what.charAt(0)); 920 final char firstUp = Character.toUpperCase(what.charAt(0)); 921 922 for (int i = src.length() - length; i >= 0; i--) { 923 // Quick check before calling the more expensive regionMatches() method: 924 final char ch = src.charAt(i); 925 if (ch != firstLo && ch != firstUp) { 926 continue; 927 } 928 929 if (src.regionMatches(true, i, what, 0, length)) { 930 return true; 931 } 932 } 933 934 return false; 935 } 936 937 /** 938 * Outputs the bytes in human readable format in units of KB,MB,GB etc. 939 * 940 * @param locale The locale to apply during formatting. If l is {@code null} then no localization is applied. 941 * @param bytes number of bytes 942 * @return human readable output 943 * @see java.lang.String#format(Locale, String, Object...) 944 */ 945 public static String humanReadableBytes(Locale locale, long bytes) { 946 int unit = 1024; 947 if (bytes < unit) { 948 return bytes + " B"; 949 } 950 int exp = (int) (Math.log(bytes) / Math.log(unit)); 951 String pre = String.valueOf("KMGTPE".charAt(exp - 1)); 952 return String.format(locale, "%.1f %sB", bytes / Math.pow(unit, exp), pre); 953 } 954 955 /** 956 * Outputs the bytes in human readable format in units of KB,MB,GB etc. 957 * 958 * The locale always used is the one returned by {@link java.util.Locale#getDefault()}. 959 * 960 * @param bytes number of bytes 961 * @return human readable output 962 * @see org.apache.camel.util.StringHelper#humanReadableBytes(Locale, long) 963 */ 964 public static String humanReadableBytes(long bytes) { 965 return humanReadableBytes(Locale.getDefault(), bytes); 966 } 967 968 /** 969 * Check for string pattern matching with a number of strategies in the following order: 970 * 971 * - equals - null pattern always matches - * always matches - Ant style matching - Regexp 972 * 973 * @param pattern the pattern 974 * @param target the string to test 975 * @return true if target matches the pattern 976 */ 977 public static boolean matches(String pattern, String target) { 978 if (Objects.equals(pattern, target)) { 979 return true; 980 } 981 982 if (Objects.isNull(pattern)) { 983 return true; 984 } 985 986 if (Objects.equals("*", pattern)) { 987 return true; 988 } 989 990 if (AntPathMatcher.INSTANCE.match(pattern, target)) { 991 return true; 992 } 993 994 Pattern p = Pattern.compile(pattern); 995 Matcher m = p.matcher(target); 996 997 return m.matches(); 998 } 999 1000 /** 1001 * Converts the string from camel case into dash format (helloGreatWorld -> hello-great-world) 1002 * 1003 * @param text the string 1004 * @return the string camel cased 1005 */ 1006 public static String camelCaseToDash(String text) { 1007 if (text == null || text.isEmpty()) { 1008 return text; 1009 } 1010 StringBuilder answer = new StringBuilder(); 1011 1012 Character prev = null; 1013 Character next = null; 1014 char[] arr = text.toCharArray(); 1015 for (int i = 0; i < arr.length; i++) { 1016 char ch = arr[i]; 1017 if (i < arr.length - 1) { 1018 next = arr[i + 1]; 1019 } else { 1020 next = null; 1021 } 1022 if (ch == '-' || ch == '_') { 1023 answer.append("-"); 1024 } else if (Character.isUpperCase(ch) && prev != null && !Character.isUpperCase(prev)) { 1025 if (prev != '-' && prev != '_') { 1026 answer.append("-"); 1027 } 1028 answer.append(ch); 1029 } else if (Character.isUpperCase(ch) && prev != null && next != null && Character.isLowerCase(next)) { 1030 if (prev != '-' && prev != '_') { 1031 answer.append("-"); 1032 } 1033 answer.append(ch); 1034 } else { 1035 answer.append(ch); 1036 } 1037 prev = ch; 1038 } 1039 1040 return answer.toString().toLowerCase(Locale.ENGLISH); 1041 } 1042 1043 /** 1044 * Does the string starts with the given prefix (ignore case). 1045 * 1046 * @param text the string 1047 * @param prefix the prefix 1048 */ 1049 public static boolean startsWithIgnoreCase(String text, String prefix) { 1050 if (text != null && prefix != null) { 1051 return prefix.length() <= text.length() && text.regionMatches(true, 0, prefix, 0, prefix.length()); 1052 } else { 1053 return text == null && prefix == null; 1054 } 1055 } 1056 1057 /** 1058 * Converts the value to an enum constant value that is in the form of upper cased with underscore. 1059 */ 1060 public static String asEnumConstantValue(String value) { 1061 if (value == null || value.isEmpty()) { 1062 return value; 1063 } 1064 value = StringHelper.camelCaseToDash(value); 1065 // replace double dashes 1066 value = value.replaceAll("-+", "-"); 1067 // replace dash with underscore and upper case 1068 value = value.replace('-', '_').toUpperCase(Locale.ENGLISH); 1069 return value; 1070 } 1071 1072 /** 1073 * Split the text on words, eg hello/world => becomes array with hello in index 0, and world in index 1. 1074 */ 1075 public static String[] splitWords(String text) { 1076 return text.split("[\\W]+"); 1077 } 1078 1079 /** 1080 * Creates a stream from the given input sequence around matches of the regex 1081 * 1082 * @param text the input 1083 * @param regex the expression used to split the input 1084 * @return the stream of strings computed by splitting the input with the given regex 1085 */ 1086 public static Stream<String> splitAsStream(CharSequence text, String regex) { 1087 if (text == null || regex == null) { 1088 return Stream.empty(); 1089 } 1090 1091 return Pattern.compile(regex).splitAsStream(text); 1092 } 1093 1094 /** 1095 * Returns the occurrence of a search string in to a string. 1096 * 1097 * @param text the text 1098 * @param search the string to search 1099 * @return an integer reporting the number of occurrence of the searched string in to the text 1100 */ 1101 public static int countOccurrence(String text, String search) { 1102 int lastIndex = 0; 1103 int count = 0; 1104 while (lastIndex != -1) { 1105 lastIndex = text.indexOf(search, lastIndex); 1106 if (lastIndex != -1) { 1107 count++; 1108 lastIndex += search.length(); 1109 } 1110 } 1111 return count; 1112 } 1113 1114 /** 1115 * Replaces a string in to a text starting from his second occurrence. 1116 * 1117 * @param text the text 1118 * @param search the string to search 1119 * @param replacement the replacement for the string 1120 * @return the string with the replacement 1121 */ 1122 public static String replaceFromSecondOccurrence(String text, String search, String replacement) { 1123 int index = text.indexOf(search); 1124 boolean replace = false; 1125 1126 while (index != -1) { 1127 String tempString = text.substring(index); 1128 if (replace) { 1129 tempString = tempString.replaceFirst(search, replacement); 1130 text = text.substring(0, index) + tempString; 1131 replace = false; 1132 } else { 1133 replace = true; 1134 } 1135 index = text.indexOf(search, index + 1); 1136 } 1137 return text; 1138 } 1139 1140 /** 1141 * Pad the string with leading spaces 1142 * 1143 * @param level level (2 blanks per level) 1144 */ 1145 public static String padString(int level) { 1146 return padString(level, 2); 1147 } 1148 1149 /** 1150 * Pad the string with leading spaces 1151 * 1152 * @param level level 1153 * @param blanks number of blanks per level 1154 */ 1155 public static String padString(int level, int blanks) { 1156 if (level == 0) { 1157 return ""; 1158 } else { 1159 return " ".repeat(level * blanks); 1160 } 1161 } 1162 1163 /** 1164 * Fills the string with repeating chars 1165 * 1166 * @param ch the char 1167 * @param count number of chars 1168 */ 1169 public static String fillChars(char ch, int count) { 1170 if (count <= 0) { 1171 return ""; 1172 } else { 1173 return Character.toString(ch).repeat(count); 1174 } 1175 } 1176 1177 public static boolean isDigit(String s) { 1178 for (char ch : s.toCharArray()) { 1179 if (!Character.isDigit(ch)) { 1180 return false; 1181 } 1182 } 1183 return true; 1184 } 1185 1186}