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