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