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