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 if (ObjectHelper.isEmpty(s)) { 171 return false; 172 } 173 174 if (s.startsWith("'") && s.endsWith("'")) { 175 return true; 176 } 177 if (s.startsWith("\"") && s.endsWith("\"")) { 178 return true; 179 } 180 181 return false; 182 } 183 184 /** 185 * Encodes the text into safe XML by replacing < > and & with XML tokens 186 * 187 * @param text the text 188 * @return the encoded text 189 */ 190 public static String xmlEncode(String text) { 191 if (text == null) { 192 return ""; 193 } 194 // must replace amp first, so we dont replace < to amp later 195 text = text.replace("&", "&"); 196 text = text.replace("\"", """); 197 text = text.replace("<", "<"); 198 text = text.replace(">", ">"); 199 return text; 200 } 201 202 /** 203 * Determines if the string has at least one letter in upper case 204 * 205 * @param text the text 206 * @return <tt>true</tt> if at least one letter is upper case, <tt>false</tt> otherwise 207 */ 208 public static boolean hasUpperCase(String text) { 209 if (text == null) { 210 return false; 211 } 212 213 for (int i = 0; i < text.length(); i++) { 214 char ch = text.charAt(i); 215 if (Character.isUpperCase(ch)) { 216 return true; 217 } 218 } 219 220 return false; 221 } 222 223 /** 224 * Determines if the string is a fully qualified class name 225 */ 226 public static boolean isClassName(String text) { 227 boolean result = false; 228 if (text != null) { 229 String[] split = text.split("\\."); 230 if (split.length > 0) { 231 String lastToken = split[split.length - 1]; 232 if (lastToken.length() > 0) { 233 result = Character.isUpperCase(lastToken.charAt(0)); 234 } 235 } 236 } 237 return result; 238 } 239 240 /** 241 * Does the expression have the language start token? 242 * 243 * @param expression the expression 244 * @param language the name of the language, such as simple 245 * @return <tt>true</tt> if the expression contains the start token, <tt>false</tt> otherwise 246 */ 247 public static boolean hasStartToken(String expression, String language) { 248 if (expression == null) { 249 return false; 250 } 251 252 // for the simple language the expression start token could be "${" 253 if ("simple".equalsIgnoreCase(language) && expression.contains("${")) { 254 return true; 255 } 256 257 if (language != null && expression.contains("$" + language + "{")) { 258 return true; 259 } 260 261 return false; 262 } 263 264 /** 265 * Replaces the first from token in the given input string. 266 * <p/> 267 * This implementation is not recursive, not does it check for tokens in the replacement string. 268 * 269 * @param input the input string 270 * @param from the from string, must <b>not</b> be <tt>null</tt> or empty 271 * @param to the replacement string, must <b>not</b> be empty 272 * @return the replaced string, or the input string if no replacement was needed 273 * @throws IllegalArgumentException if the input arguments is invalid 274 */ 275 public static String replaceFirst(String input, String from, String to) { 276 int pos = input.indexOf(from); 277 if (pos != -1) { 278 int len = from.length(); 279 return input.substring(0, pos) + to + input.substring(pos + len); 280 } else { 281 return input; 282 } 283 } 284 285 /** 286 * Creates a json tuple with the given name/value pair. 287 * 288 * @param name the name 289 * @param value the value 290 * @param isMap whether the tuple should be map 291 * @return the json 292 */ 293 public static String toJson(String name, String value, boolean isMap) { 294 if (isMap) { 295 return "{ " + StringQuoteHelper.doubleQuote(name) + ": " + StringQuoteHelper.doubleQuote(value) + " }"; 296 } else { 297 return StringQuoteHelper.doubleQuote(name) + ": " + StringQuoteHelper.doubleQuote(value); 298 } 299 } 300 301 /** 302 * Asserts whether the string is <b>not</b> empty. 303 * 304 * @param value the string to test 305 * @param name the key that resolved the value 306 * @return the passed {@code value} as is 307 * @throws IllegalArgumentException is thrown if assertion fails 308 */ 309 public static String notEmpty(String value, String name) { 310 if (ObjectHelper.isEmpty(value)) { 311 throw new IllegalArgumentException(name + " must be specified and not empty"); 312 } 313 314 return value; 315 } 316 317 /** 318 * Asserts whether the string is <b>not</b> empty. 319 * 320 * @param value the string to test 321 * @param on additional description to indicate where this problem occurred (appended as 322 * toString()) 323 * @param name the key that resolved the value 324 * @return the passed {@code value} as is 325 * @throws IllegalArgumentException is thrown if assertion fails 326 */ 327 public static String notEmpty(String value, String name, Object on) { 328 if (on == null) { 329 ObjectHelper.notNull(value, name); 330 } else if (ObjectHelper.isEmpty(value)) { 331 throw new IllegalArgumentException(name + " must be specified and not empty on: " + on); 332 } 333 334 return value; 335 } 336 337 public static String[] splitOnCharacter(String value, String needle, int count) { 338 String[] rc = new String[count]; 339 rc[0] = value; 340 for (int i = 1; i < count; i++) { 341 String v = rc[i - 1]; 342 int p = v.indexOf(needle); 343 if (p < 0) { 344 return rc; 345 } 346 rc[i - 1] = v.substring(0, p); 347 rc[i] = v.substring(p + 1); 348 } 349 return rc; 350 } 351 352 public static Iterator<String> splitOnCharacterAsIterator(String value, char needle, int count) { 353 // skip leading and trailing needles 354 int end = value.length() - 1; 355 boolean skipStart = value.charAt(0) == needle; 356 boolean skipEnd = value.charAt(end) == needle; 357 if (skipStart && skipEnd) { 358 value = value.substring(1, end); 359 count = count - 2; 360 } else if (skipStart) { 361 value = value.substring(1); 362 count = count - 1; 363 } else if (skipEnd) { 364 value = value.substring(0, end); 365 count = count - 1; 366 } 367 368 final int size = count; 369 final String text = value; 370 371 return new Iterator<String>() { 372 int i; 373 int pos; 374 375 @Override 376 public boolean hasNext() { 377 return i < size; 378 } 379 380 @Override 381 public String next() { 382 if (i == size) { 383 throw new NoSuchElementException(); 384 } 385 String answer; 386 int end = text.indexOf(needle, pos); 387 if (end != -1) { 388 answer = text.substring(pos, end); 389 pos = end + 1; 390 } else { 391 answer = text.substring(pos); 392 // no more data 393 i = size; 394 } 395 return answer; 396 } 397 }; 398 } 399 400 public static List<String> splitOnCharacterAsList(String value, char needle, int count) { 401 // skip leading and trailing needles 402 int end = value.length() - 1; 403 boolean skipStart = value.charAt(0) == needle; 404 boolean skipEnd = value.charAt(end) == needle; 405 if (skipStart && skipEnd) { 406 value = value.substring(1, end); 407 count = count - 2; 408 } else if (skipStart) { 409 value = value.substring(1); 410 count = count - 1; 411 } else if (skipEnd) { 412 value = value.substring(0, end); 413 count = count - 1; 414 } 415 416 List<String> rc = new ArrayList<>(count); 417 int pos = 0; 418 for (int i = 0; i < count; i++) { 419 end = value.indexOf(needle, pos); 420 if (end != -1) { 421 String part = value.substring(pos, end); 422 pos = end + 1; 423 rc.add(part); 424 } else { 425 rc.add(value.substring(pos)); 426 break; 427 } 428 } 429 return rc; 430 } 431 432 /** 433 * Removes any starting characters on the given text which match the given character 434 * 435 * @param text the string 436 * @param ch the initial characters to remove 437 * @return either the original string or the new substring 438 */ 439 public static String removeStartingCharacters(String text, char ch) { 440 int idx = 0; 441 while (text.charAt(idx) == ch) { 442 idx++; 443 } 444 if (idx > 0) { 445 return text.substring(idx); 446 } 447 return text; 448 } 449 450 /** 451 * Capitalize the string (upper case first character) 452 * 453 * @param text the string 454 * @return the string capitalized (upper case first character) 455 */ 456 public static String capitalize(String text) { 457 return capitalize(text, false); 458 } 459 460 /** 461 * Capitalize the string (upper case first character) 462 * 463 * @param text the string 464 * @param dashToCamelCase whether to also convert dash format into camel case (hello-great-world -> 465 * helloGreatWorld) 466 * @return the string capitalized (upper case first character) 467 */ 468 public static String capitalize(String text, boolean dashToCamelCase) { 469 if (dashToCamelCase) { 470 text = dashToCamelCase(text); 471 } 472 if (text == null) { 473 return null; 474 } 475 int length = text.length(); 476 if (length == 0) { 477 return text; 478 } 479 String answer = text.substring(0, 1).toUpperCase(Locale.ENGLISH); 480 if (length > 1) { 481 answer += text.substring(1, length); 482 } 483 return answer; 484 } 485 486 /** 487 * Converts the string from dash format into camel case (hello-great-world -> helloGreatWorld) 488 * 489 * @param text the string 490 * @return the string camel cased 491 */ 492 public static String dashToCamelCase(String text) { 493 if (text == null) { 494 return null; 495 } 496 int length = text.length(); 497 if (length == 0) { 498 return text; 499 } 500 if (text.indexOf('-') == -1) { 501 return text; 502 } 503 504 // there is at least 1 dash so the capacity can be shorter 505 StringBuilder sb = new StringBuilder(length - 1); 506 boolean upper = false; 507 for (int i = 0; i < length; i++) { 508 char c = text.charAt(i); 509 if (c == '-') { 510 upper = true; 511 } else { 512 if (upper) { 513 c = Character.toUpperCase(c); 514 } 515 sb.append(c); 516 upper = false; 517 } 518 } 519 return sb.toString(); 520 } 521 522 /** 523 * Returns the string after the given token 524 * 525 * @param text the text 526 * @param after the token 527 * @return the text after the token, or <tt>null</tt> if text does not contain the token 528 */ 529 public static String after(String text, String after) { 530 int pos = text.indexOf(after); 531 if (pos == -1) { 532 return null; 533 } 534 return text.substring(pos + after.length()); 535 } 536 537 /** 538 * Returns the string after the given token, or the default value 539 * 540 * @param text the text 541 * @param after the token 542 * @param defaultValue the value to return if text does not contain the token 543 * @return the text after the token, or the supplied defaultValue if text does not contain the token 544 */ 545 public static String after(String text, String after, String defaultValue) { 546 String answer = after(text, after); 547 return answer != null ? answer : defaultValue; 548 } 549 550 /** 551 * Returns an object after the given token 552 * 553 * @param text the text 554 * @param after the token 555 * @param mapper a mapping function to convert the string after the token to type T 556 * @return an Optional describing the result of applying a mapping function to the text after the token. 557 */ 558 public static <T> Optional<T> after(String text, String after, Function<String, T> mapper) { 559 String result = after(text, after); 560 if (result == null) { 561 return Optional.empty(); 562 } else { 563 return Optional.ofNullable(mapper.apply(result)); 564 } 565 } 566 567 /** 568 * Returns the string after the the last occurrence of the given token 569 * 570 * @param text the text 571 * @param after the token 572 * @return the text after the token, or <tt>null</tt> if text does not contain the token 573 */ 574 public static String afterLast(String text, String after) { 575 int pos = text.lastIndexOf(after); 576 if (pos == -1) { 577 return null; 578 } 579 return text.substring(pos + after.length()); 580 } 581 582 /** 583 * Returns the string after the the last occurrence of the given token, or the default value 584 * 585 * @param text the text 586 * @param after the token 587 * @param defaultValue the value to return if text does not contain the token 588 * @return the text after the token, or the supplied defaultValue if text does not contain the token 589 */ 590 public static String afterLast(String text, String after, String defaultValue) { 591 String answer = afterLast(text, after); 592 return answer != null ? answer : defaultValue; 593 } 594 595 /** 596 * Returns the string before the given token 597 * 598 * @param text the text 599 * @param before the token 600 * @return the text before the token, or <tt>null</tt> if text does not contain the token 601 */ 602 public static String before(String text, String before) { 603 int pos = text.indexOf(before); 604 return pos == -1 ? null : text.substring(0, pos); 605 } 606 607 /** 608 * Returns the string before the given token, or the default value 609 * 610 * @param text the text 611 * @param before the token 612 * @param defaultValue the value to return if text does not contain the token 613 * @return the text before the token, or the supplied defaultValue if text does not contain the token 614 */ 615 public static String before(String text, String before, String defaultValue) { 616 String answer = before(text, before); 617 return answer != null ? answer : defaultValue; 618 } 619 620 /** 621 * Returns an object before the given token 622 * 623 * @param text the text 624 * @param before the token 625 * @param mapper a mapping function to convert the string before the token to type T 626 * @return an Optional describing the result of applying a mapping function to the text before the token. 627 */ 628 public static <T> Optional<T> before(String text, String before, Function<String, T> mapper) { 629 String result = before(text, before); 630 if (result == null) { 631 return Optional.empty(); 632 } else { 633 return Optional.ofNullable(mapper.apply(result)); 634 } 635 } 636 637 /** 638 * Returns the string before the last occurrence of the given token 639 * 640 * @param text the text 641 * @param before the token 642 * @return the text before the token, or <tt>null</tt> if text does not contain the token 643 */ 644 public static String beforeLast(String text, String before) { 645 int pos = text.lastIndexOf(before); 646 return pos == -1 ? null : text.substring(0, pos); 647 } 648 649 /** 650 * Returns the string before the last occurrence of the given token, or the default value 651 * 652 * @param text the text 653 * @param before the token 654 * @param defaultValue the value to return if text does not contain the token 655 * @return the text before the token, or the supplied defaultValue if text does not contain the token 656 */ 657 public static String beforeLast(String text, String before, String defaultValue) { 658 String answer = beforeLast(text, before); 659 return answer != null ? answer : defaultValue; 660 } 661 662 /** 663 * Returns the string between the given tokens 664 * 665 * @param text the text 666 * @param after the before token 667 * @param before the after token 668 * @return the text between the tokens, or <tt>null</tt> if text does not contain the tokens 669 */ 670 public static String between(String text, String after, String before) { 671 text = after(text, after); 672 if (text == null) { 673 return null; 674 } 675 return before(text, before); 676 } 677 678 /** 679 * Returns an object between the given token 680 * 681 * @param text the text 682 * @param after the before token 683 * @param before the after token 684 * @param mapper a mapping function to convert the string between the token to type T 685 * @return an Optional describing the result of applying a mapping function to the text between the token. 686 */ 687 public static <T> Optional<T> between(String text, String after, String before, Function<String, T> mapper) { 688 String result = between(text, after, before); 689 if (result == null) { 690 return Optional.empty(); 691 } else { 692 return Optional.ofNullable(mapper.apply(result)); 693 } 694 } 695 696 /** 697 * Returns the string between the most outer pair of tokens 698 * <p/> 699 * The number of token pairs must be evenly, eg there must be same number of before and after tokens, otherwise 700 * <tt>null</tt> is returned 701 * <p/> 702 * This implementation skips matching when the text is either single or double quoted. For example: 703 * <tt>${body.matches("foo('bar')")</tt> Will not match the parenthesis from the quoted text. 704 * 705 * @param text the text 706 * @param after the before token 707 * @param before the after token 708 * @return the text between the outer most tokens, or <tt>null</tt> if text does not contain the tokens 709 */ 710 public static String betweenOuterPair(String text, char before, char after) { 711 if (text == null) { 712 return null; 713 } 714 715 int pos = -1; 716 int pos2 = -1; 717 int count = 0; 718 int count2 = 0; 719 720 boolean singleQuoted = false; 721 boolean doubleQuoted = false; 722 for (int i = 0; i < text.length(); i++) { 723 char ch = text.charAt(i); 724 if (!doubleQuoted && ch == '\'') { 725 singleQuoted = !singleQuoted; 726 } else if (!singleQuoted && ch == '\"') { 727 doubleQuoted = !doubleQuoted; 728 } 729 if (singleQuoted || doubleQuoted) { 730 continue; 731 } 732 733 if (ch == before) { 734 count++; 735 } else if (ch == after) { 736 count2++; 737 } 738 739 if (ch == before && pos == -1) { 740 pos = i; 741 } else if (ch == after) { 742 pos2 = i; 743 } 744 } 745 746 if (pos == -1 || pos2 == -1) { 747 return null; 748 } 749 750 // must be even paris 751 if (count != count2) { 752 return null; 753 } 754 755 return text.substring(pos + 1, pos2); 756 } 757 758 /** 759 * Returns an object between the most outer pair of tokens 760 * 761 * @param text the text 762 * @param after the before token 763 * @param before the after token 764 * @param mapper a mapping function to convert the string between the most outer pair of tokens to type T 765 * @return an Optional describing the result of applying a mapping function to the text between the most 766 * outer pair of tokens. 767 */ 768 public static <T> Optional<T> betweenOuterPair(String text, char before, char after, Function<String, T> mapper) { 769 String result = betweenOuterPair(text, before, after); 770 if (result == null) { 771 return Optional.empty(); 772 } else { 773 return Optional.ofNullable(mapper.apply(result)); 774 } 775 } 776 777 /** 778 * Returns true if the given name is a valid java identifier 779 */ 780 public static boolean isJavaIdentifier(String name) { 781 if (name == null) { 782 return false; 783 } 784 int size = name.length(); 785 if (size < 1) { 786 return false; 787 } 788 if (Character.isJavaIdentifierStart(name.charAt(0))) { 789 for (int i = 1; i < size; i++) { 790 if (!Character.isJavaIdentifierPart(name.charAt(i))) { 791 return false; 792 } 793 } 794 return true; 795 } 796 return false; 797 } 798 799 /** 800 * Cleans the string to a pure Java identifier so we can use it for loading class names. 801 * <p/> 802 * Especially from Spring DSL people can have \n \t or other characters that otherwise would result in 803 * ClassNotFoundException 804 * 805 * @param name the class name 806 * @return normalized classname that can be load by a class loader. 807 */ 808 public static String normalizeClassName(String name) { 809 StringBuilder sb = new StringBuilder(name.length()); 810 for (char ch : name.toCharArray()) { 811 if (ch == '.' || ch == '[' || ch == ']' || ch == '-' || Character.isJavaIdentifierPart(ch)) { 812 sb.append(ch); 813 } 814 } 815 return sb.toString(); 816 } 817 818 /** 819 * Compares old and new text content and report back which lines are changed 820 * 821 * @param oldText the old text 822 * @param newText the new text 823 * @return a list of line numbers that are changed in the new text 824 */ 825 public static List<Integer> changedLines(String oldText, String newText) { 826 if (oldText == null || oldText.equals(newText)) { 827 return Collections.emptyList(); 828 } 829 830 List<Integer> changed = new ArrayList<>(); 831 832 String[] oldLines = oldText.split("\n"); 833 String[] newLines = newText.split("\n"); 834 835 for (int i = 0; i < newLines.length; i++) { 836 String newLine = newLines[i]; 837 String oldLine = i < oldLines.length ? oldLines[i] : null; 838 if (oldLine == null) { 839 changed.add(i); 840 } else if (!newLine.equals(oldLine)) { 841 changed.add(i); 842 } 843 } 844 845 return changed; 846 } 847 848 /** 849 * Removes the leading and trailing whitespace and if the resulting string is empty returns {@code null}. Examples: 850 * <p> 851 * Examples: <blockquote> 852 * 853 * <pre> 854 * trimToNull("abc") -> "abc" 855 * trimToNull(" abc") -> "abc" 856 * trimToNull(" abc ") -> "abc" 857 * trimToNull(" ") -> null 858 * trimToNull("") -> null 859 * </pre> 860 * 861 * </blockquote> 862 */ 863 public static String trimToNull(final String given) { 864 if (given == null) { 865 return null; 866 } 867 868 final String trimmed = given.trim(); 869 870 if (trimmed.isEmpty()) { 871 return null; 872 } 873 874 return trimmed; 875 } 876 877 /** 878 * Checks if the src string contains what 879 * 880 * @param src is the source string to be checked 881 * @param what is the string which will be looked up in the src argument 882 * @return true/false 883 */ 884 public static boolean containsIgnoreCase(String src, String what) { 885 if (src == null || what == null) { 886 return false; 887 } 888 889 final int length = what.length(); 890 if (length == 0) { 891 return true; // Empty string is contained 892 } 893 894 final char firstLo = Character.toLowerCase(what.charAt(0)); 895 final char firstUp = Character.toUpperCase(what.charAt(0)); 896 897 for (int i = src.length() - length; i >= 0; i--) { 898 // Quick check before calling the more expensive regionMatches() method: 899 final char ch = src.charAt(i); 900 if (ch != firstLo && ch != firstUp) { 901 continue; 902 } 903 904 if (src.regionMatches(true, i, what, 0, length)) { 905 return true; 906 } 907 } 908 909 return false; 910 } 911 912 /** 913 * Outputs the bytes in human readable format in units of KB,MB,GB etc. 914 * 915 * @param locale The locale to apply during formatting. If l is {@code null} then no localization is applied. 916 * @param bytes number of bytes 917 * @return human readable output 918 * @see java.lang.String#format(Locale, String, Object...) 919 */ 920 public static String humanReadableBytes(Locale locale, long bytes) { 921 int unit = 1024; 922 if (bytes < unit) { 923 return bytes + " B"; 924 } 925 int exp = (int) (Math.log(bytes) / Math.log(unit)); 926 String pre = "KMGTPE".charAt(exp - 1) + ""; 927 return String.format(locale, "%.1f %sB", bytes / Math.pow(unit, exp), pre); 928 } 929 930 /** 931 * Outputs the bytes in human readable format in units of KB,MB,GB etc. 932 * 933 * The locale always used is the one returned by {@link java.util.Locale#getDefault()}. 934 * 935 * @param bytes number of bytes 936 * @return human readable output 937 * @see org.apache.camel.util.StringHelper#humanReadableBytes(Locale, long) 938 */ 939 public static String humanReadableBytes(long bytes) { 940 return humanReadableBytes(Locale.getDefault(), bytes); 941 } 942 943 /** 944 * Check for string pattern matching with a number of strategies in the following order: 945 * 946 * - equals - null pattern always matches - * always matches - Ant style matching - Regexp 947 * 948 * @param pattern the pattern 949 * @param target the string to test 950 * @return true if target matches the pattern 951 */ 952 public static boolean matches(String pattern, String target) { 953 if (Objects.equals(pattern, target)) { 954 return true; 955 } 956 957 if (Objects.isNull(pattern)) { 958 return true; 959 } 960 961 if (Objects.equals("*", pattern)) { 962 return true; 963 } 964 965 if (AntPathMatcher.INSTANCE.match(pattern, target)) { 966 return true; 967 } 968 969 Pattern p = Pattern.compile(pattern); 970 Matcher m = p.matcher(target); 971 972 return m.matches(); 973 } 974 975 /** 976 * Converts the string from camel case into dash format (helloGreatWorld -> hello-great-world) 977 * 978 * @param text the string 979 * @return the string camel cased 980 */ 981 public static String camelCaseToDash(String text) { 982 if (text == null || text.isEmpty()) { 983 return text; 984 } 985 StringBuilder answer = new StringBuilder(); 986 987 Character prev = null; 988 Character next = null; 989 char[] arr = text.toCharArray(); 990 for (int i = 0; i < arr.length; i++) { 991 char ch = arr[i]; 992 if (i < arr.length - 1) { 993 next = arr[i + 1]; 994 } else { 995 next = null; 996 } 997 if (ch == '-' || ch == '_') { 998 answer.append("-"); 999 } else if (Character.isUpperCase(ch) && prev != null && !Character.isUpperCase(prev)) { 1000 if (prev != '-' && prev != '_') { 1001 answer.append("-"); 1002 } 1003 answer.append(ch); 1004 } else if (Character.isUpperCase(ch) && prev != null && next != null && Character.isLowerCase(next)) { 1005 if (prev != '-' && prev != '_') { 1006 answer.append("-"); 1007 } 1008 answer.append(ch); 1009 } else { 1010 answer.append(ch); 1011 } 1012 prev = ch; 1013 } 1014 1015 return answer.toString().toLowerCase(Locale.ENGLISH); 1016 } 1017 1018 /** 1019 * Does the string starts with the given prefix (ignore case). 1020 * 1021 * @param text the string 1022 * @param prefix the prefix 1023 */ 1024 public static boolean startsWithIgnoreCase(String text, String prefix) { 1025 if (text != null && prefix != null) { 1026 return prefix.length() > text.length() ? false : text.regionMatches(true, 0, prefix, 0, prefix.length()); 1027 } else { 1028 return text == null && prefix == null; 1029 } 1030 } 1031 1032 /** 1033 * Converts the value to an enum constant value that is in the form of upper cased with underscore. 1034 */ 1035 public static String asEnumConstantValue(String value) { 1036 if (value == null || value.isEmpty()) { 1037 return value; 1038 } 1039 value = StringHelper.camelCaseToDash(value); 1040 // replace double dashes 1041 value = value.replaceAll("-+", "-"); 1042 // replace dash with underscore and upper case 1043 value = value.replace('-', '_').toUpperCase(Locale.ENGLISH); 1044 return value; 1045 } 1046 1047 /** 1048 * Split the text on words, eg hello/world => becomes array with hello in index 0, and world in index 1. 1049 */ 1050 public static String[] splitWords(String text) { 1051 return text.split("[\\W]+"); 1052 } 1053 1054 /** 1055 * Creates a stream from the given input sequence around matches of the regex 1056 * 1057 * @param text the input 1058 * @param regex the expression used to split the input 1059 * @return the stream of strings computed by splitting the input with the given regex 1060 */ 1061 public static Stream<String> splitAsStream(CharSequence text, String regex) { 1062 if (text == null || regex == null) { 1063 return Stream.empty(); 1064 } 1065 1066 return Pattern.compile(regex).splitAsStream(text); 1067 } 1068 1069 /** 1070 * Returns the occurrence of a search string in to a string. 1071 * 1072 * @param text the text 1073 * @param search the string to search 1074 * @return an integer reporting the number of occurrence of the searched string in to the text 1075 */ 1076 public static int countOccurrence(String text, String search) { 1077 int lastIndex = 0; 1078 int count = 0; 1079 while (lastIndex != -1) { 1080 lastIndex = text.indexOf(search, lastIndex); 1081 if (lastIndex != -1) { 1082 count++; 1083 lastIndex += search.length(); 1084 } 1085 } 1086 return count; 1087 } 1088 1089 /** 1090 * Replaces a string in to a text starting from his second occurrence. 1091 * 1092 * @param text the text 1093 * @param search the string to search 1094 * @param replacement the replacement for the string 1095 * @return the string with the replacement 1096 */ 1097 public static String replaceFromSecondOccurrence(String text, String search, String replacement) { 1098 int index = text.indexOf(search); 1099 boolean replace = false; 1100 1101 while (index != -1) { 1102 String tempString = text.substring(index); 1103 if (replace) { 1104 tempString = tempString.replaceFirst(search, replacement); 1105 text = text.substring(0, index) + tempString; 1106 replace = false; 1107 } else { 1108 replace = true; 1109 } 1110 index = text.indexOf(search, index + 1); 1111 } 1112 return text; 1113 } 1114}