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