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