001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019package lucee.commons.lang; 020 021import java.nio.charset.CharsetEncoder; 022import java.util.HashMap; 023import java.util.Iterator; 024import java.util.Map; 025 026import lucee.runtime.exp.PageException; 027import lucee.runtime.op.Caster; 028import lucee.runtime.type.Collection; 029import lucee.runtime.type.util.ArrayUtil; 030 031 032 033 034 035/** 036 * Util to do some additional String Operations 037 */ 038public final class StringUtil { 039 040 private static final char[] SPECIAL_WHITE_SPACE_CHARS=new char[]{ 041 0x85 // NEL, Next line 042 ,0xa0 // no-break space 043 ,0x1680 // ogham space mark 044 ,0x180e // mongolian vowel separator 045 ,0x2000 // en quad 046 ,0x2001 // em quad 047 ,0x2002 // en space 048 ,0x2003 // em space 049 ,0x2004 // three-per-em space 050 ,0x2005 // four-per-em space 051 ,0x2006 // six-per-em space 052 ,0x2007 // figure space 053 ,0x2008 // punctuation space 054 ,0x2009 // thin space 055 ,0x200A // hair space 056 ,0x2028 // line separator 057 ,0x2029 // paragraph separator 058 ,0x202F // narrow no-break space 059 ,0x205F // medium mathematical space 060 ,0x3000 // ideographic space 061 }; 062 063 064 /** 065 * do first Letter Upper case 066 * @param str String to operate 067 * @return uppercase string 068 */ 069 public static String ucFirst(String str) { 070 if(str==null) return null; 071 else if(str.length()<=1) return str.toUpperCase(); 072 else { 073 return str.substring(0,1).toUpperCase()+str.substring(1); 074 } 075 } 076 077 078 public static String capitalize( String input, char[] delims ) { 079 080 if (isEmpty(input)) return input; 081 082 if (ArrayUtil.isEmpty(delims)) 083 delims = new char[]{ '.', '-', '(', ')' }; 084 085 StringBuilder sb = new StringBuilder( input.length() ); 086 087 boolean isLastDelim = true,isLastSpace = true; 088 int len=input.length(); 089 for (int i=0; i<len; i++) { 090 091 char c = input.charAt( i ); 092 093 if ( Character.isWhitespace(c) ) { 094 095 if ( !isLastSpace ) 096 sb.append( ' ' ); 097 098 isLastSpace = true; 099 } 100 else { 101 102 sb.append( ( isLastSpace || isLastDelim ) ? Character.toUpperCase( c ) : c ); 103 104 isLastDelim = _contains(delims, c ); 105 isLastSpace = false; 106 } 107 } 108 109 return sb.toString(); 110 } 111 112 113 private static boolean _contains(char[] chars, char c) { 114 for ( int i=0; i<chars.length; i++ ) { 115 if(chars[i]==c) return true; 116 } 117 return false; 118 } 119 120 121 /** 122 * do first Letter Upper case 123 * @param str String to operate 124 * @return lower case String 125 */ 126 public static String lcFirst(String str) { 127 if(str==null) return null; 128 else if(str.length()<=1) return str.toLowerCase(); 129 else { 130 return str.substring(0,1).toLowerCase()+str.substring(1); 131 } 132 } 133 134 /** 135 * Unescapes HTML Tags 136 * @param html html code to escape 137 * @return escaped html code 138 */ 139 public static String unescapeHTML(String html) { 140 return HTMLEntities.unescapeHTML(html); 141 } 142 143 /** 144 * Escapes XML Tags 145 * @param html html code to unescape 146 * @return unescaped html code 147 */ 148 public static String escapeHTML(String html) { 149 return HTMLEntities.escapeHTML(html); 150 } 151 152 /** 153 * escapes JS sensitive characters 154 * @param str String to escape 155 * @return escapes String 156 */ 157 public static String escapeJS(String str, char quotesUsed) { 158 return escapeJS(str,quotesUsed, (CharsetEncoder)null); 159 } 160 161 public static String escapeJS(String str, char quotesUsed, java.nio.charset.Charset charset) { 162 return escapeJS(str, quotesUsed, charset==null?null:charset.newEncoder()); 163 } 164 165 /** 166 * escapes JS sensitive characters 167 * @param str String to escape 168 * @param charset if not null, it checks if the given string is supported by the encoding, if not, lucee encodes the string 169 * @return escapes String 170 */ 171 public static String escapeJS(String str, char quotesUsed, CharsetEncoder enc) { 172 char[] arr=str.toCharArray(); 173 StringBuilder rtn=new StringBuilder(arr.length); 174 rtn.append(quotesUsed); 175 176 for(int i=0;i<arr.length;i++) { 177 if(arr[i] < 128){ 178 switch(arr[i]) { 179 case '\\': rtn.append("\\\\"); break; 180 case '\n': rtn.append("\\n"); break; 181 case '\r': rtn.append("\\r"); break; 182 case '\f': rtn.append("\\f"); break; 183 case '\b': rtn.append("\\b"); break; 184 case '\t': rtn.append("\\t"); break; 185 case '"' : 186 if(quotesUsed=='"') rtn.append("\\\""); 187 else rtn.append('"'); 188 break; 189 case '\'': 190 if(quotesUsed=='\'') rtn.append("\\\'"); 191 else rtn.append('\''); 192 break; 193 case '/': 194 // escape </script> 195 if( 196 i>0 && arr[i-1]=='<' 197 && i+1<arr.length && arr[i+1]=='s' 198 && i+2<arr.length && arr[i+2]=='c' 199 && i+3<arr.length && arr[i+3]=='r' 200 && i+4<arr.length && arr[i+4]=='i' 201 && i+5<arr.length && arr[i+5]=='p' 202 && i+6<arr.length && arr[i+6]=='t' 203 && i+7<arr.length && (isWhiteSpace(arr[i+7]) || arr[i+7]=='>') 204 205 ) { 206 rtn.append("\\/"); 207 break; 208 } 209 210 default : rtn.append(arr[i]); break; 211 } 212 } 213 else if(enc==null || !enc.canEncode(arr[i])) { 214 if (arr[i] < 0x10) rtn.append("\\u000"); 215 else if (arr[i] < 0x100) rtn.append( "\\u00"); 216 else if (arr[i] < 0x1000) rtn.append( "\\u0"); 217 else rtn.append( "\\u"); 218 rtn.append(Integer.toHexString(arr[i])); 219 } 220 else { 221 rtn.append(arr[i]); 222 } 223 } 224 return rtn.append(quotesUsed).toString(); 225 } 226 227 /** 228 * reapeats a string 229 * @param str string to repeat 230 * @param count how many time string will be reapeted 231 * @return reapted string 232 */ 233 public static String repeatString(String str,int count) { 234 if(count<=0) return ""; 235 char[] chars = str.toCharArray(); 236 char[] rtn=new char[chars.length*count]; 237 int pos=0; 238 for(int i=0;i<count;i++) { 239 for(int y=0;y<chars.length;y++)rtn[pos++]=chars[y]; 240 //rtn.append(str); 241 } 242 return new String(rtn); 243 } 244 245 /** 246 * translate, like method toString, a object to a string, but when value is null value will be translated to a empty String (""). 247 * @param o Object to convert 248 * @return converted String 249 */ 250 public static String toStringEmptyIfNull(Object o) { 251 if(o==null)return ""; 252 return o.toString(); 253 } 254 255 public static String emptyIfNull(String str) { 256 if(str==null)return ""; 257 return str; 258 } 259 260 public static String emptyIfNull(Collection.Key key) { 261 if(key==null)return ""; 262 return key.getString(); 263 } 264 265 /** 266 * escape all special characters of the regular expresson language 267 * @param str String to escape 268 * @return escaped String 269 */ 270 public static String reqExpEscape(String str) { 271 char[] arr = str.toCharArray(); 272 StringBuilder sb=new StringBuilder(str.length()*2); 273 274 for(int i=0;i<arr.length;i++) { 275 sb.append('\\'); 276 sb.append(arr[i]); 277 } 278 279 return sb.toString(); 280 } 281 282 /** 283 * translate a string to a valid identity variable name 284 * @param varName variable name template to translate 285 * @return translated variable name 286 */ 287 public static String toIdentityVariableName(String varName) { 288 char[] chars=varName.toCharArray(); 289 long changes=0; 290 291 StringBuilder rtn=new StringBuilder(chars.length+2); 292 rtn.append("CF"); 293 294 for(int i=0;i<chars.length;i++) { 295 char c=chars[i]; 296 if((c>='a' && c<='z') ||(c>='A' && c<='Z') ||(c>='0' && c<='9')) 297 rtn.append(c); 298 else { 299 rtn.append('_'); 300 changes+=(c*(i+1)); 301 } 302 } 303 304 return rtn.append(changes).toString(); 305 } 306 /** 307 * translate a string to a valid classname string 308 * @param str string to translate 309 * @return translated String 310 */ 311 public static String toClassName(String str) { 312 StringBuilder rtn=new StringBuilder(); 313 String[] arr=str.split("[\\\\|//]"); 314 for(int i=0;i<arr.length;i++) { 315 if(arr[i].length()==0)continue; 316 if(rtn.length()!=0)rtn.append('.'); 317 char[] chars=arr[i].toCharArray(); 318 long changes=0; 319 for(int y=0;y<chars.length;y++) { 320 char c=chars[y]; 321 if(y==0 && (c>='0' && c<='9'))rtn.append("_"+c); 322 else if((c>='a' && c<='z') ||(c>='A' && c<='Z') ||(c>='0' && c<='9')) 323 rtn.append(c); 324 else { 325 rtn.append('_'); 326 changes+=(c*(i+1)); 327 } 328 } 329 if(changes>0)rtn.append(changes); 330 } 331 return rtn.toString(); 332 } 333 334 /** 335 * translate a string to a valid variable string 336 * @param str string to translate 337 * @return translated String 338 */ 339 public static String toVariableName(String str) { 340 return toVariableName(str, true,false); 341 } 342 343 344 public static String toJavaClassName(String str) { 345 return toVariableName(str, true, true); 346 } 347 348 public static String toVariableName(String str, boolean addIdentityNumber, boolean allowDot) { 349 350 StringBuilder rtn=new StringBuilder(); 351 char[] chars=str.toCharArray(); 352 long changes=0; 353 boolean doCorrect=true; 354 for(int i=0;i<chars.length;i++) { 355 char c=chars[i]; 356 if(i==0 && (c>='0' && c<='9'))rtn.append("_"+c); 357 else if((c>='a' && c<='z') ||(c>='A' && c<='Z') ||(c>='0' && c<='9') || c=='_' || c=='$' || (allowDot && c=='.')) 358 rtn.append(c); 359 else { 360 doCorrect=false; 361 rtn.append('_'); 362 changes+=(c*(i+1)); 363 } 364 } 365 366 if(addIdentityNumber && changes>0)rtn.append(changes); 367 //print.ln(" - "+rtn); 368 369 if(doCorrect)return correctReservedWord(rtn.toString()); 370 return rtn.toString(); 371 } 372 373 374 /** 375 * if given string is a keyword it will be replaced with none keyword 376 * @param str 377 * @return corrected word 378 */ 379 private static String correctReservedWord(String str) { 380 char first=str.charAt(0); 381 382 switch(first) { 383 case 'a': 384 if(str.equals("abstract")) return "_"+str; 385 break; 386 case 'b': 387 if(str.equals("boolean")) return "_"+str; 388 else if(str.equals("break")) return "_"+str; 389 else if(str.equals("byte")) return "_"+str; 390 break; 391 case 'c': 392 if(str.equals("case")) return "_"+str; 393 else if(str.equals("catch")) return "_"+str; 394 else if(str.equals("char")) return "_"+str; 395 else if(str.equals("const")) return "_"+str; 396 else if(str.equals("class")) return "_"+str; 397 else if(str.equals("continue")) return "_"+str; 398 break; 399 case 'd': 400 if(str.equals("default")) return "_"+str; 401 else if(str.equals("do")) return "_"+str; 402 else if(str.equals("double")) return "_"+str; 403 break; 404 case 'e': 405 if(str.equals("else")) return "_"+str; 406 else if(str.equals("extends")) return "_"+str; 407 else if(str.equals("enum")) return "_"+str; 408 break; 409 case 'f': 410 if(str.equals("false")) return "_"+str; 411 else if(str.equals("final")) return "_"+str; 412 else if(str.equals("finally")) return "_"+str; 413 else if(str.equals("float")) return "_"+str; 414 else if(str.equals("for")) return "_"+str; 415 break; 416 case 'g': 417 if(str.equals("goto")) return "_"+str; 418 break; 419 case 'i': 420 if(str.equals("if")) return "_"+str; 421 else if(str.equals("implements")) return "_"+str; 422 else if(str.equals("import")) return "_"+str; 423 else if(str.equals("instanceof")) return "_"+str; 424 else if(str.equals("int")) return "_"+str; 425 else if(str.equals("interface")) return "_"+str; 426 break; 427 case 'n': 428 if(str.equals("native")) return "_"+str; 429 else if(str.equals("new")) return "_"+str; 430 else if(str.equals("null")) return "_"+str; 431 break; 432 case 'p': 433 if(str.equals("package")) return "_"+str; 434 else if(str.equals("private")) return "_"+str; 435 else if(str.equals("protected")) return "_"+str; 436 else if(str.equals("public")) return "_"+str; 437 break; 438 case 'r': 439 if(str.equals("return")) return "_"+str; 440 break; 441 case 's': 442 if(str.equals("short")) return "_"+str; 443 else if(str.equals("static")) return "_"+str; 444 else if(str.equals("strictfp")) return "_"+str; 445 else if(str.equals("super")) return "_"+str; 446 else if(str.equals("switch")) return "_"+str; 447 else if(str.equals("synchronized")) return "_"+str; 448 break; 449 case 't': 450 if(str.equals("this")) return "_"+str; 451 else if(str.equals("throw")) return "_"+str; 452 else if(str.equals("throws")) return "_"+str; 453 else if(str.equals("transient")) return "_"+str; 454 else if(str.equals("true")) return "_"+str; 455 else if(str.equals("try")) return "_"+str; 456 break; 457 case 'v': 458 if(str.equals("void")) return "_"+str; 459 else if(str.equals("volatile")) return "_"+str; 460 break; 461 case 'w': 462 if(str.equals("while")) return "_"+str; 463 break; 464 } 465 return str; 466 467 } 468 469 /** 470 * This function returns a string with whitespace stripped from the beginning of str 471 * @param str String to clean 472 * @return cleaned String 473 */ 474 public static String ltrim(String str,String defaultValue) { 475 if(str==null) return defaultValue; 476 int len = str.length(); 477 int st = 0; 478 479 while ((st < len) && (str.charAt(st) <= ' ')) { 480 st++; 481 } 482 return ((st > 0)) ? str.substring(st) : str; 483 } 484 485 /** 486 * This function returns a string with whitespace stripped from the end of str 487 * @param str String to clean 488 * @return cleaned String 489 */ 490 public static String rtrim(String str,String defaultValue) { 491 if(str==null) return defaultValue; 492 int len = str.length(); 493 494 while ((0 < len) && (str.charAt(len-1) <= ' ')) { 495 len--; 496 } 497 return (len < str.length()) ? str.substring(0, len) : str; 498 } 499 500 /** 501 * trim given value, return defaultvalue when input is null 502 * @param str 503 * @param defaultValue 504 * @return trimmed string or defaultValue 505 */ 506 public static String trim(String str,String defaultValue) { 507 if(str==null) return defaultValue; 508 return str.trim(); 509 } 510 511 /** 512 * 513 * @param c character to check 514 * @param checkSpecialWhiteSpace if set to true, lucee checks also uncommon white spaces. 515 * @return 516 */ 517 public static boolean isWhiteSpace(char c,boolean checkSpecialWhiteSpace) { 518 if(Character.isWhitespace(c)) return true; 519 if(checkSpecialWhiteSpace) { 520 for(int i=0;i<SPECIAL_WHITE_SPACE_CHARS.length;i++){ 521 if(c==SPECIAL_WHITE_SPACE_CHARS[i]) return true; 522 } 523 } 524 return false; 525 } 526 527 528 public static boolean isWhiteSpace(char c) { 529 return isWhiteSpace(c,false); 530 } 531 532 /** 533 * trim given value, return defaultvalue when input is null 534 * this function no only removes the "classic" whitespaces, 535 * it also removes Byte order masks forgotten to remove when reading a UTF file. 536 * @param str 537 * @param removeBOM if set to true, Byte Order Mask that got forgotten get removed as well 538 * @param removeSpecialWhiteSpace if set to true, lucee removes also uncommon white spaces. 539 * @param defaultValue 540 * @return trimmed string or defaultValue 541 */ 542 public static String trim(String str,boolean removeBOM,boolean removeSpecialWhiteSpace,String defaultValue) { 543 if(str==null) return defaultValue; 544 if(str.isEmpty()) return str; 545 // remove leading BOM Marks 546 if(removeBOM) { 547 // UTF-16, big-endian 548 if (str.charAt(0) == '\uFEFF') str=str.substring(1); 549 else if (str.charAt(0) == '\uFFFD') str=str.substring(1); 550 551 // UTF-16, little-endian 552 else if (str.charAt(0) == '\uFFFE') str=str.substring(1); 553 554 // UTF-8 555 else if(str.length()>=2) { 556 // TODO i get this from UTF-8 files generated by suplime text, i was expecting something else 557 if (str.charAt(0) == '\uBBEF' && str.charAt(1) == '\uFFFD') str=str.substring(2); 558 } 559 } 560 561 if(removeSpecialWhiteSpace) { 562 int len = str.length(); 563 int startIndex = 0,endIndex=len-1; 564 // left 565 while ((startIndex < len) && isWhiteSpace(str.charAt(startIndex),true)) { 566 startIndex++; 567 } 568 // right 569 while ((startIndex < endIndex) && isWhiteSpace(str.charAt(endIndex),true)) { 570 endIndex--; 571 } 572 return ((startIndex > 0) || (endIndex+1 < len)) ? str.substring(startIndex, endIndex+1) : str; 573 } 574 575 return str.trim(); 576 } 577 578 /** 579 * return if in a string are line feeds or not 580 * @param str string to check 581 * @return translated string 582 */ 583 public static boolean hasLineFeed(String str) { 584 int len=str.length(); 585 char c; 586 for(int i=0;i<len;i++) { 587 c=str.charAt(i); 588 if(c=='\n' || c=='\r') return true; 589 } 590 return false; 591 } 592 593 /** 594 * remove all white spaces followd by whitespaces 595 * @param str strring to translate 596 * @return translated string 597 */ 598 public static String suppressWhiteSpace(String str) { 599 int len=str.length(); 600 StringBuilder sb=new StringBuilder(len); 601 //boolean wasWS=false; 602 603 char c; 604 char buffer=0; 605 for(int i=0;i<len;i++) { 606 c=str.charAt(i); 607 if(c=='\n' || c=='\r') buffer='\n'; 608 else if(isWhiteSpace(c)) { 609 if(buffer==0)buffer=c; 610 } 611 else { 612 if(buffer!=0){ 613 sb.append(buffer); 614 buffer=0; 615 } 616 sb.append(c); 617 } 618 //sb.append(c); 619 } 620 if(buffer!=0)sb.append(buffer); 621 622 return sb.toString(); 623 } 624 625 626 627 /** 628 * returns string, if given string is null or lengt 0 return default value 629 * @param value 630 * @param defaultValue 631 * @return value or default value 632 */ 633 public static String toString(String value, String defaultValue) { 634 return value==null || value.length()==0?defaultValue:value; 635 } 636 637 /** 638 * returns string, if given string is null or lengt 0 return default value 639 * @param value 640 * @param defaultValue 641 * @return value or default value 642 */ 643 public static String toString(Object value, String defaultValue) { 644 if(value==null) return defaultValue; 645 return toString(value.toString(), defaultValue); 646 } 647 648 /** 649 * cut string to max size if the string is greater, otherweise to nothing 650 * @param content 651 * @param max 652 * @return cutted string 653 */ 654 655 public static String max(String content,int max) { 656 return max(content, max,""); 657 } 658 659 public static String max(String content,int max, String dotDotDot) { 660 if(content==null) return null; 661 if(content.length()<=max) return content; 662 663 return content.substring(0,max)+dotDotDot; 664 } 665 666 667 /** 668 * performs a replace operation on a string 669 * 670 * @param input - the string input to work on 671 * @param find - the substring to find 672 * @param repl - the substring to replace the matches with 673 * @param firstOnly - if true then only the first occurrence of {@code find} will be replaced 674 * @param ignoreCase - if true then matches will not be case sensitive 675 * @return 676 */ 677 public static String replace( String input, String find, String repl, boolean firstOnly, boolean ignoreCase ) { 678 679 int findLen = find.length(); 680 681 if ( findLen == 0 ) 682 return input; 683 684 String scan = input; 685 686 if ( ignoreCase ) { 687 688 scan = scan.toLowerCase(); 689 find = find.toLowerCase(); 690 } 691 else if ( findLen == repl.length() ) { 692 693 if ( find.equals( repl ) ) 694 return input; 695 696 if ( !firstOnly && findLen == 1 ) 697 return input.replace( find.charAt(0), repl.charAt(0) ); 698 } 699 700 int pos = scan.indexOf( find ); 701 702 if (pos == -1) 703 return input; 704 705 int start = 0; 706 StringBuilder sb = new StringBuilder( repl.length() > find.length() ? (int)Math.ceil( input.length() * 1.2 ) : input.length() ); 707 708 while ( pos != -1 ) { 709 710 sb.append( input.substring( start, pos ) ); 711 sb.append( repl ); 712 713 start = pos + findLen; 714 715 if ( firstOnly ) 716 break; 717 718 pos = scan.indexOf( find, start ); 719 } 720 721 if ( input.length() > start ) 722 sb.append( input.substring( start ) ); 723 724 return sb.toString(); 725 } 726 727 728 /** 729 * maintains the legacy signature of this method where matches are CaSe sensitive (sets the default of ignoreCase to false). 730 * 731 * @param input - the string input to work on 732 * @param find - the substring to find 733 * @param repl - the substring to replace the matches with 734 * @param firstOnly - if true then only the first occurrence of {@code find} will be replaced 735 * @return - calls replace( input, find, repl, firstOnly, false ) 736 */ 737 public static String replace( String input, String find, String repl, boolean firstOnly ) { 738 739 return replace( input, find, repl, firstOnly, false ); 740 } 741 742 743 /** 744 * performs a CaSe sensitive replace all 745 * 746 * @param input - the string input to work on 747 * @param find - the substring to find 748 * @param repl - the substring to replace the matches with 749 * @return - calls replace( input, find, repl, false, false ) 750 */ 751 public static String replace( String input, String find, String repl ) { 752 753 return replace( input, find, repl, false, false ); 754 } 755 756 757 /** 758 * adds zeros add the begin of a int example: addZeros(2,3) return "002" 759 * @param i number to add nulls 760 * @param size 761 * @return min len of return value; 762 */ 763 public static String addZeros(int i, int size) { 764 String rtn=Caster.toString(i); 765 if(rtn.length()<size) return repeatString("0",size-rtn.length())+rtn; 766 return rtn; 767 } 768 769 770 /** 771 * adds zeros add the begin of a int example: addZeros(2,3) return "002" 772 * @param i number to add nulls 773 * @param size 774 * @return min len of return value; 775 */ 776 public static String addZeros(long i, int size) { 777 String rtn=Caster.toString(i); 778 if(rtn.length()<size) return repeatString("0",size-rtn.length())+rtn; 779 return rtn; 780 } 781 782 public static int indexOf(String haystack, String needle) { 783 if(haystack==null) return -1; 784 return haystack.indexOf(needle); 785 } 786 787 public static int indexOfIgnoreCase(String haystack, String needle) { 788 if(StringUtil.isEmpty(haystack) || StringUtil.isEmpty(needle)) return -1; 789 needle=needle.toLowerCase(); 790 791 int lenHaystack=haystack.length(); 792 int lenNeedle=needle.length(); 793 794 char lastNeedle=needle.charAt(lenNeedle-1); 795 char c; 796 outer:for(int i=lenNeedle-1;i<lenHaystack;i++) { 797 c=Character.toLowerCase(haystack.charAt(i)); 798 if(c==lastNeedle) { 799 for(int y=0;y<lenNeedle-1;y++) { 800 if(needle.charAt(y)!=Character.toLowerCase(haystack.charAt(i-(lenNeedle-1)+y))) 801 continue outer; 802 } 803 return i-(lenNeedle-1); 804 } 805 } 806 807 808 return -1; 809 } 810 811 /** 812 * Tests if this string starts with the specified prefix. 813 * @param str string to check first char 814 * @param prefix the prefix. 815 * @return is first of given type 816 */ 817 public static boolean startsWith(String str, char prefix) { 818 return str!=null && str.length()>0 && str.charAt(0)==prefix; 819 } 820 821 public static boolean startsWith(String str, char prefix1, char prefix2) { 822 return str!=null && str.length()>0 && (str.charAt(0)==prefix1 || str.charAt(0)==prefix2); 823 } 824 825 /** 826 * Tests if this string ends with the specified suffix. 827 * @param str string to check first char 828 * @param suffix the suffix. 829 * @return is last of given type 830 */ 831 public static boolean endsWith(String str, char suffix) { 832 return str!=null && str.length()>0 && str.charAt(str.length()-1)==suffix; 833 } 834 835 public static boolean endsWith(String str, char prefix1, char prefix2) { 836 return str!=null && str.length()>0 && (str.charAt(str.length()-1)==prefix1 || str.charAt(str.length()-1)==prefix2); 837 } 838 839 /** 840 * Tests if this string ends with the specified suffix. 841 * @param str string to check first char 842 * @param suffix the suffix. 843 * @return is last of given type 844 */ 845 /** 846 * Helper functions to query a strings start portion. The comparison is case insensitive. 847 * 848 * @param base the base string. 849 * @param start the starting text. 850 * 851 * @return true, if the string starts with the given starting text. 852 */ 853 public static boolean startsWithIgnoreCase(final String base, final String start) { 854 if (base.length() < start.length()) { 855 return false; 856 } 857 return base.regionMatches(true, 0, start, 0, start.length()); 858 } 859 860 /** 861 * Helper functions to query a strings end portion. The comparison is case insensitive. 862 * 863 * @param base the base string. 864 * @param end the ending text. 865 * 866 * @return true, if the string ends with the given ending text. 867 */ 868 public static boolean endsWithIgnoreCase(final String base, final String end) { 869 if (base.length() < end.length()) { 870 return false; 871 } 872 return base.regionMatches(true, base.length() - end.length(), end, 0, end.length()); 873 } 874 875 876 877 /** 878 * returns if byte arr is a BOM character Stream (UTF-8,UTF-16) 879 * @param barr 880 * @return is BOM or not 881 */ 882 public static boolean isBOM(byte[] barr) { 883 return barr.length>=3 && barr[0]==0xEF && barr[1]==0xBB && barr[2]==0xBF; 884 } 885 886 /** 887 * return "" if value is null otherwise return same string 888 * @param str 889 * @return string (not null) 890 */ 891 public static String valueOf(String str) { 892 if(str==null)return ""; 893 return str; 894 } 895 896 897 /** 898 * cast a string a lower case String, is faster than the String.toLowerCase, if all Character are already Low Case 899 * @param str 900 * @return lower case value 901 */ 902 public static String toLowerCase(String str) { 903 int len=str.length(); 904 char c; 905 for(int i=0;i<len;i++) { 906 c=str.charAt(i); 907 if(!((c>='a' && c<='z') || (c>='0' && c<='9'))) { 908 return str.toLowerCase(); 909 } 910 } 911 912 return str; 913 } 914 public static String toUpperCase(String str) { 915 int len=str.length(); 916 char c; 917 for(int i=0;i<len;i++) { 918 c=str.charAt(i); 919 if(!((c>='A' && c<='Z') || (c>='0' && c<='9'))) { 920 return str.toUpperCase(); 921 } 922 } 923 924 return str; 925 } 926 927 /** 928 * soundex function 929 * @param str 930 * @return soundex from given string 931 */ 932 public static String soundex(String str) { 933 return new org.apache.commons.codec.language.Soundex().soundex(str); 934 } 935 936 /** 937 * return the last character of a string, if string ist empty return 0; 938 * @param str string to get last character 939 * @return last character 940 */ 941 public static char lastChar(String str) { 942 if(str==null || str.length()==0) return 0; 943 return str.charAt(str.length()-1); 944 } 945 946 947 /** 948 * 949 * @param str 950 * @return return if a String is "Empty", that means NULL or String with length 0 (whitespaces will not counted) 951 */ 952 public static boolean isEmpty(String str) { 953 return str==null || str.length()==0; 954 } 955 /** 956 * 957 * @param str 958 * @return return if a String is "Empty", that means NULL or String with length 0 (whitespaces will not counted) 959 */ 960 public static boolean isEmpty(String str, boolean trim) { 961 if(!trim) return isEmpty(str); 962 return str==null || str.trim().length()==0; 963 } 964 965 /** 966 * return the first character of a string, if string ist empty return 0; 967 * @param str string to get first character 968 * @return first character 969 */ 970 public static char firstChar(String str) { 971 if(isEmpty(str)) return 0; 972 return str.charAt(0); 973 } 974 975 public static String removeWhiteSpace(String str) { 976 if(isEmpty(str)) return str; 977 StringBuilder sb=new StringBuilder(); 978 char[] carr = str.trim().toCharArray(); 979 for(int i=0;i<carr.length;i++) { 980 if(!isWhiteSpace(carr[i]))sb.append(carr[i]); 981 } 982 return sb.toString(); 983 } 984 985 public static String replaceLast(String str, char from, char to) { 986 int index = str.lastIndexOf(from); 987 if(index==-1)return str; 988 return str.substring(0,index)+to+str.substring(index+1); 989 } 990 public static String replaceLast(String str, String from, String to) { 991 int index = str.lastIndexOf(from); 992 if(index==-1)return str; 993 return str.substring(0,index)+to+str.substring(index+from.length()); 994 } 995 996 /** 997 * removes quotes(",') that wraps the string 998 * @param string 999 * @return 1000 */ 1001 public static String removeQuotes(String string,boolean trim) { 1002 if(trim)string=string.trim(); 1003 if((StringUtil.startsWith(string, '"') && StringUtil.endsWith(string, '"')) || (StringUtil.startsWith(string, '\'') && StringUtil.endsWith(string, '\''))){ 1004 string= string.substring(1,string.length()-1); 1005 if(trim)string=string.trim(); 1006 } 1007 return string; 1008 } 1009 1010 public static boolean isEmpty(Object obj, boolean trim) { 1011 if(obj==null) return true; 1012 if(obj instanceof String)return isEmpty((String)obj,trim); 1013 if(obj instanceof StringBuffer)return isEmpty((StringBuffer)obj,trim); 1014 if(obj instanceof StringBuilder)return isEmpty((StringBuilder)obj,trim); 1015 if(obj instanceof Collection.Key)return isEmpty(((Collection.Key)obj).getString(),trim); 1016 return false; 1017 } 1018 1019 public static boolean isEmpty(Object obj) { 1020 if(obj==null) return true; 1021 if(obj instanceof String)return isEmpty((String)obj); 1022 if(obj instanceof Collection.Key)return isEmpty(((Collection.Key)obj).getString()); 1023 if(obj instanceof StringBuffer)return isEmpty((StringBuffer)obj); 1024 if(obj instanceof StringBuilder)return isEmpty((StringBuilder)obj); 1025 return false; 1026 } 1027 1028 public static boolean isEmpty(StringBuffer sb,boolean trim) { 1029 if(trim) return sb==null || sb.toString().trim().length()==0; 1030 return sb==null || sb.length()==0; 1031 } 1032 public static boolean isEmpty(StringBuilder sb,boolean trim) { 1033 if(trim) return sb==null || sb.toString().trim().length()==0; 1034 return sb==null || sb.length()==0; 1035 } 1036 1037 public static boolean isEmpty(StringBuffer sb) { 1038 return sb==null || sb.length()==0; 1039 } 1040 1041 public static boolean isEmpty(StringBuilder sb) { 1042 return sb==null || sb.length()==0; 1043 } 1044 1045 public static String removeStarting(String str, String sub) { 1046 if(isEmpty(str) || isEmpty(sub) || !str.startsWith(sub)) return str; 1047 return str.substring(sub.length()); 1048 } 1049 1050 public static String removeStartingIgnoreCase(String str, String sub) { 1051 if(isEmpty(sub) || !startsWithIgnoreCase(str, sub)) return str; 1052 return str.substring(sub.length()); 1053 } 1054 1055 1056 public static String[] merge(String str, String[] arr) { 1057 String[] narr=new String[arr.length+1]; 1058 narr[0]=str; 1059 for(int i=0;i<arr.length;i++) { 1060 narr[i+1]=arr[i]; 1061 } 1062 return narr; 1063 1064 } 1065 1066 public static int length(String str) { 1067 if(str==null) return 0; 1068 return str.length(); 1069 } 1070 1071 public static int length(String str, boolean trim) { 1072 if(str==null) return 0; 1073 return str.trim().length(); 1074 } 1075 1076 public static boolean hasUpperCase(String str) { 1077 if(isEmpty(str)) return false; 1078 return !str.equals(str.toLowerCase()); 1079 } 1080 1081 public static boolean contains(String str, String substr) { 1082 if(str==null) return false; 1083 return str.indexOf(substr)!=-1; 1084 } 1085 1086 public static boolean containsIgnoreCase(String str, String substr) { 1087 return indexOfIgnoreCase(str,substr)!=-1; 1088 } 1089 1090 public static String substringEL(String str, int index,String defaultValue) { 1091 if(str==null || index<0 || index>str.length()) return defaultValue; 1092 return str.substring(index); 1093 } 1094 1095 /** 1096 * translate a string in camel notation to a string in hypen notation 1097 * example: 1098 * helloWorld -> hello-world 1099 * @param str 1100 * @return 1101 */ 1102 public static String camelToHypenNotation(String str) { 1103 if(isEmpty(str)) return str; 1104 1105 StringBuilder sb=new StringBuilder(); 1106 //int len=str.length(); 1107 char c; 1108 1109 sb.append(Character.toLowerCase(str.charAt(0))); 1110 for(int i=1;i<str.length();i++){ 1111 c=str.charAt(i); 1112 if(Character.isUpperCase(c)){ 1113 sb.append('-'); 1114 sb.append(Character.toLowerCase(c)); 1115 } 1116 else sb.append(c); 1117 } 1118 return sb.toString(); 1119 } 1120 1121 /** 1122 * translate a string in hypen notation to a string in camel notation 1123 * example: 1124 * hello-world -> helloWorld 1125 * @param str 1126 * @return 1127 */ 1128 public static String hypenToCamelNotation(String str) { 1129 if(isEmpty(str)) return str; 1130 1131 StringBuilder sb=new StringBuilder(); 1132 int len=str.length(); 1133 char c; 1134 1135 for(int i=0;i<str.length();i++){ 1136 c=str.charAt(i); 1137 if(c=='-'){ 1138 if(len>++i) sb.append(Character.toUpperCase(str.charAt(i))); 1139 } 1140 else sb.append(c); 1141 } 1142 return sb.toString(); 1143 } 1144 1145 public static boolean isAscii(String str) { 1146 1147 if ( str == null ) 1148 return false; 1149 1150 for(int i=str.length()-1;i>=0;i--){ 1151 1152 if( str.charAt(i) > 127 ) 1153 return false; 1154 } 1155 return true; 1156 } 1157 1158 1159 /** 1160 * returns true if all characters in the string are letters 1161 * 1162 * @param str 1163 * @return 1164 */ 1165 public static boolean isAllAlpha(String str) { 1166 1167 if ( str == null ) return false; 1168 1169 for (int i=str.length()-1; i >= 0; i--) { 1170 1171 if ( !Character.isLetter( str.charAt(i) ) ) 1172 return false; 1173 } 1174 1175 return true; 1176 } 1177 1178 1179 /** 1180 * returns true if the input string has letters and they are all UPPERCASE 1181 * 1182 * @param str 1183 * @return 1184 */ 1185 public static boolean isAllUpperCase(String str) { 1186 1187 if ( str == null ) return false; 1188 1189 boolean hasLetters = false; 1190 char c; 1191 1192 for (int i=str.length()-1; i >= 0; i--) { 1193 1194 c = str.charAt(i); 1195 if ( Character.isLetter( c )) { 1196 1197 if ( !Character.isUpperCase( c )) 1198 return false; 1199 1200 hasLetters = true; 1201 } 1202 } 1203 1204 return hasLetters; 1205 } 1206 1207 1208 public static boolean isWhiteSpace(String str) { 1209 if(str==null) return false; 1210 for(int i=str.length()-1;i>=0;i--){ 1211 if(!isWhiteSpace(str.charAt(i))) return false; 1212 } 1213 return true; 1214 } 1215 1216 /** 1217 * this method works different from the regular substring method, the regular substring method takes startIndex and endIndex as second and third argument, 1218 * this method takes offset and length 1219 * @param str 1220 * @param off 1221 * @param len 1222 * @return 1223 */ 1224 public static String substring(String str, int off, int len) { 1225 return str.substring(off,off+len); 1226 } 1227 1228 1229 public static String insertAt(String str, CharSequence substring, int pos) { 1230 1231 if (isEmpty(substring)) 1232 return str; 1233 1234 int len = str.length(); 1235 1236 StringBuilder sb = new StringBuilder(len + substring.length()); 1237 1238 if (pos > len) 1239 pos = len; 1240 1241 if (pos > 0) 1242 sb.append(str.substring(0, pos)); 1243 1244 sb.append(substring); 1245 sb.append(str.substring(pos)); 1246 1247 return sb.toString(); 1248 } 1249 1250 1251 /** 1252 * this is the public entry point for the replaceMap() method 1253 * 1254 * @param input - the string on which the replacements should be performed. 1255 * @param map - a java.util.Map with key/value pairs where the key is the substring to find and the value is the substring with which to replace the matched key 1256 * @param ignoreCase - if true then matches will not be case sensitive 1257 * @return 1258 * @throws PageException 1259 */ 1260 public static String replaceMap( String input, Map map, boolean ignoreCase ) throws PageException { 1261 1262 return replaceMap( input, map, ignoreCase, true ); 1263 } 1264 1265 1266 /** 1267 * this is the core of the replaceMap() method. 1268 * 1269 * it is called once from the public entry point and then internally from resolveInternals() 1270 * 1271 * when doResolveInternals is true -- this method calls resolveInternals. therefore, calls from resolveInternals() 1272 * must pass false to that param to avoid an infinite ping-pong loop 1273 * 1274 * @param input - the string on which the replacements should be performed. 1275 * @param map - a java.util.Map with key/value pairs where the key is the substring to find and the value is the substring with which to replace the matched key 1276 * @param ignoreCase - if true then matches will not be case sensitive 1277 * @param doResolveInternals - only the initial call (from the public entry point) should pass true 1278 * @return 1279 * @throws PageException 1280 */ 1281 private static String replaceMap( String input, Map map, boolean ignoreCase, boolean doResolveInternals ) throws PageException { 1282 if ( doResolveInternals ) 1283 map = resolveInternals( map, ignoreCase, 0 ); 1284 1285 String result = input; 1286 Iterator<Map.Entry> it = map.entrySet().iterator(); 1287 Map.Entry e; 1288 while ( it.hasNext() ) { 1289 e = it.next(); 1290 result = replace( result, Caster.toString(e.getKey()), Caster.toString(e.getValue()), false, ignoreCase ); 1291 } 1292 return result; 1293 } 1294 1295 1296 1297 /** 1298 * resolves internal values within the map, so if the map has a key "{signature}" 1299 * and its value is "Team {group}" and there's a key with the value {group} whose 1300 * value is "Lucee", then {signature} will resolve to "Team Lucee". 1301 * 1302 * {signature} = "Team {group}" 1303 * {group} = "Lucee" 1304 * 1305 * then signature will resolve to 1306 * 1307 * {signature} = "Team Lucee" 1308 * 1309 * @param map - key/value pairs for find key/replace with value 1310 * @param ignoreCase - if true then matches will not be case sensitive 1311 * @param count - used internally as safety valve to ensure that we don't go into infinite loop if two values reference each-other 1312 * @return 1313 * @throws PageException 1314 */ 1315 private static Map resolveInternals( Map map, boolean ignoreCase, int count ) throws PageException { 1316 Map result = new HashMap(); 1317 Iterator<Map.Entry> it = map.entrySet().iterator(); 1318 boolean isModified = false; 1319 Map.Entry e; 1320 String v,r; 1321 while ( it.hasNext() ) { 1322 e = it.next(); 1323 v = Caster.toString( e.getValue() ); 1324 r = replaceMap( v, map, ignoreCase, false ); // pass false for last arg so that replaceMap() will not call this method in an infinite loop 1325 result.put( Caster.toString( e.getKey() ), r ); 1326 if ( !v.equalsIgnoreCase( r ) ) 1327 isModified = true; 1328 } 1329 1330 if ( isModified && count++ < map.size() ) 1331 result = resolveInternals( result, ignoreCase, count ); // recursive call 1332 1333 return result; 1334 } 1335 1336 1337 public static String toStringNative(Object obj,String defaultValue) { 1338 return obj==null?defaultValue:obj.toString(); 1339 } 1340}