001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package org.apache.hadoop.util; 020 021 import java.io.PrintWriter; 022 import java.io.StringWriter; 023 import java.net.URI; 024 import java.net.URISyntaxException; 025 import java.text.DateFormat; 026 import java.text.DecimalFormat; 027 import java.text.NumberFormat; 028 import java.util.ArrayList; 029 import java.util.Arrays; 030 import java.util.Collection; 031 import java.util.Date; 032 import java.util.Iterator; 033 import java.util.List; 034 import java.util.Locale; 035 import java.util.StringTokenizer; 036 037 import org.apache.hadoop.classification.InterfaceAudience; 038 import org.apache.hadoop.classification.InterfaceStability; 039 import org.apache.hadoop.fs.Path; 040 import org.apache.hadoop.net.NetUtils; 041 042 /** 043 * General string utils 044 */ 045 @InterfaceAudience.Private 046 @InterfaceStability.Unstable 047 public class StringUtils { 048 049 /** 050 * Priority of the StringUtils shutdown hook. 051 */ 052 public static final int SHUTDOWN_HOOK_PRIORITY = 0; 053 054 private static final DecimalFormat decimalFormat; 055 static { 056 NumberFormat numberFormat = NumberFormat.getNumberInstance(Locale.ENGLISH); 057 decimalFormat = (DecimalFormat) numberFormat; 058 decimalFormat.applyPattern("#.##"); 059 } 060 061 /** 062 * Make a string representation of the exception. 063 * @param e The exception to stringify 064 * @return A string with exception name and call stack. 065 */ 066 public static String stringifyException(Throwable e) { 067 StringWriter stm = new StringWriter(); 068 PrintWriter wrt = new PrintWriter(stm); 069 e.printStackTrace(wrt); 070 wrt.close(); 071 return stm.toString(); 072 } 073 074 /** 075 * Given a full hostname, return the word upto the first dot. 076 * @param fullHostname the full hostname 077 * @return the hostname to the first dot 078 */ 079 public static String simpleHostname(String fullHostname) { 080 int offset = fullHostname.indexOf('.'); 081 if (offset != -1) { 082 return fullHostname.substring(0, offset); 083 } 084 return fullHostname; 085 } 086 087 private static DecimalFormat oneDecimal = new DecimalFormat("0.0"); 088 089 /** 090 * Given an integer, return a string that is in an approximate, but human 091 * readable format. 092 * It uses the bases 'k', 'm', and 'g' for 1024, 1024**2, and 1024**3. 093 * @param number the number to format 094 * @return a human readable form of the integer 095 */ 096 public static String humanReadableInt(long number) { 097 long absNumber = Math.abs(number); 098 double result = number; 099 String suffix = ""; 100 if (absNumber < 1024) { 101 // since no division has occurred, don't format with a decimal point 102 return String.valueOf(number); 103 } else if (absNumber < 1024 * 1024) { 104 result = number / 1024.0; 105 suffix = "k"; 106 } else if (absNumber < 1024 * 1024 * 1024) { 107 result = number / (1024.0 * 1024); 108 suffix = "m"; 109 } else { 110 result = number / (1024.0 * 1024 * 1024); 111 suffix = "g"; 112 } 113 return oneDecimal.format(result) + suffix; 114 } 115 116 /** 117 * Format a percentage for presentation to the user. 118 * @param done the percentage to format (0.0 to 1.0) 119 * @param digits the number of digits past the decimal point 120 * @return a string representation of the percentage 121 */ 122 public static String formatPercent(double done, int digits) { 123 DecimalFormat percentFormat = new DecimalFormat("0.00%"); 124 double scale = Math.pow(10.0, digits+2); 125 double rounded = Math.floor(done * scale); 126 percentFormat.setDecimalSeparatorAlwaysShown(false); 127 percentFormat.setMinimumFractionDigits(digits); 128 percentFormat.setMaximumFractionDigits(digits); 129 return percentFormat.format(rounded / scale); 130 } 131 132 /** 133 * Given an array of strings, return a comma-separated list of its elements. 134 * @param strs Array of strings 135 * @return Empty string if strs.length is 0, comma separated list of strings 136 * otherwise 137 */ 138 139 public static String arrayToString(String[] strs) { 140 if (strs.length == 0) { return ""; } 141 StringBuilder sbuf = new StringBuilder(); 142 sbuf.append(strs[0]); 143 for (int idx = 1; idx < strs.length; idx++) { 144 sbuf.append(","); 145 sbuf.append(strs[idx]); 146 } 147 return sbuf.toString(); 148 } 149 150 /** 151 * Given an array of bytes it will convert the bytes to a hex string 152 * representation of the bytes 153 * @param bytes 154 * @param start start index, inclusively 155 * @param end end index, exclusively 156 * @return hex string representation of the byte array 157 */ 158 public static String byteToHexString(byte[] bytes, int start, int end) { 159 if (bytes == null) { 160 throw new IllegalArgumentException("bytes == null"); 161 } 162 StringBuilder s = new StringBuilder(); 163 for(int i = start; i < end; i++) { 164 s.append(String.format("%02x", bytes[i])); 165 } 166 return s.toString(); 167 } 168 169 /** Same as byteToHexString(bytes, 0, bytes.length). */ 170 public static String byteToHexString(byte bytes[]) { 171 return byteToHexString(bytes, 0, bytes.length); 172 } 173 174 /** 175 * Given a hexstring this will return the byte array corresponding to the 176 * string 177 * @param hex the hex String array 178 * @return a byte array that is a hex string representation of the given 179 * string. The size of the byte array is therefore hex.length/2 180 */ 181 public static byte[] hexStringToByte(String hex) { 182 byte[] bts = new byte[hex.length() / 2]; 183 for (int i = 0; i < bts.length; i++) { 184 bts[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16); 185 } 186 return bts; 187 } 188 /** 189 * 190 * @param uris 191 */ 192 public static String uriToString(URI[] uris){ 193 if (uris == null) { 194 return null; 195 } 196 StringBuilder ret = new StringBuilder(uris[0].toString()); 197 for(int i = 1; i < uris.length;i++){ 198 ret.append(","); 199 ret.append(uris[i].toString()); 200 } 201 return ret.toString(); 202 } 203 204 /** 205 * @param str 206 * The string array to be parsed into an URI array. 207 * @return <tt>null</tt> if str is <tt>null</tt>, else the URI array 208 * equivalent to str. 209 * @throws IllegalArgumentException 210 * If any string in str violates RFC 2396. 211 */ 212 public static URI[] stringToURI(String[] str){ 213 if (str == null) 214 return null; 215 URI[] uris = new URI[str.length]; 216 for (int i = 0; i < str.length;i++){ 217 try{ 218 uris[i] = new URI(str[i]); 219 }catch(URISyntaxException ur){ 220 throw new IllegalArgumentException( 221 "Failed to create uri for " + str[i], ur); 222 } 223 } 224 return uris; 225 } 226 227 /** 228 * 229 * @param str 230 */ 231 public static Path[] stringToPath(String[] str){ 232 if (str == null) { 233 return null; 234 } 235 Path[] p = new Path[str.length]; 236 for (int i = 0; i < str.length;i++){ 237 p[i] = new Path(str[i]); 238 } 239 return p; 240 } 241 /** 242 * 243 * Given a finish and start time in long milliseconds, returns a 244 * String in the format Xhrs, Ymins, Z sec, for the time difference between two times. 245 * If finish time comes before start time then negative valeus of X, Y and Z wil return. 246 * 247 * @param finishTime finish time 248 * @param startTime start time 249 */ 250 public static String formatTimeDiff(long finishTime, long startTime){ 251 long timeDiff = finishTime - startTime; 252 return formatTime(timeDiff); 253 } 254 255 /** 256 * 257 * Given the time in long milliseconds, returns a 258 * String in the format Xhrs, Ymins, Z sec. 259 * 260 * @param timeDiff The time difference to format 261 */ 262 public static String formatTime(long timeDiff){ 263 StringBuilder buf = new StringBuilder(); 264 long hours = timeDiff / (60*60*1000); 265 long rem = (timeDiff % (60*60*1000)); 266 long minutes = rem / (60*1000); 267 rem = rem % (60*1000); 268 long seconds = rem / 1000; 269 270 if (hours != 0){ 271 buf.append(hours); 272 buf.append("hrs, "); 273 } 274 if (minutes != 0){ 275 buf.append(minutes); 276 buf.append("mins, "); 277 } 278 // return "0sec if no difference 279 buf.append(seconds); 280 buf.append("sec"); 281 return buf.toString(); 282 } 283 /** 284 * Formats time in ms and appends difference (finishTime - startTime) 285 * as returned by formatTimeDiff(). 286 * If finish time is 0, empty string is returned, if start time is 0 287 * then difference is not appended to return value. 288 * @param dateFormat date format to use 289 * @param finishTime fnish time 290 * @param startTime start time 291 * @return formatted value. 292 */ 293 public static String getFormattedTimeWithDiff(DateFormat dateFormat, 294 long finishTime, long startTime){ 295 StringBuilder buf = new StringBuilder(); 296 if (0 != finishTime) { 297 buf.append(dateFormat.format(new Date(finishTime))); 298 if (0 != startTime){ 299 buf.append(" (" + formatTimeDiff(finishTime , startTime) + ")"); 300 } 301 } 302 return buf.toString(); 303 } 304 305 /** 306 * Returns an arraylist of strings. 307 * @param str the comma seperated string values 308 * @return the arraylist of the comma seperated string values 309 */ 310 public static String[] getStrings(String str){ 311 Collection<String> values = getStringCollection(str); 312 if(values.size() == 0) { 313 return null; 314 } 315 return values.toArray(new String[values.size()]); 316 } 317 318 /** 319 * Returns a collection of strings. 320 * @param str comma seperated string values 321 * @return an <code>ArrayList</code> of string values 322 */ 323 public static Collection<String> getStringCollection(String str){ 324 List<String> values = new ArrayList<String>(); 325 if (str == null) 326 return values; 327 StringTokenizer tokenizer = new StringTokenizer (str,","); 328 values = new ArrayList<String>(); 329 while (tokenizer.hasMoreTokens()) { 330 values.add(tokenizer.nextToken()); 331 } 332 return values; 333 } 334 335 /** 336 * Splits a comma separated value <code>String</code>, trimming leading and trailing whitespace on each value. 337 * @param str a comma separated <String> with values 338 * @return a <code>Collection</code> of <code>String</code> values 339 */ 340 public static Collection<String> getTrimmedStringCollection(String str){ 341 return new ArrayList<String>( 342 Arrays.asList(getTrimmedStrings(str))); 343 } 344 345 /** 346 * Splits a comma separated value <code>String</code>, trimming leading and trailing whitespace on each value. 347 * @param str a comma separated <String> with values 348 * @return an array of <code>String</code> values 349 */ 350 public static String[] getTrimmedStrings(String str){ 351 if (null == str || "".equals(str.trim())) { 352 return emptyStringArray; 353 } 354 355 return str.trim().split("\\s*,\\s*"); 356 } 357 358 final public static String[] emptyStringArray = {}; 359 final public static char COMMA = ','; 360 final public static String COMMA_STR = ","; 361 final public static char ESCAPE_CHAR = '\\'; 362 363 /** 364 * Split a string using the default separator 365 * @param str a string that may have escaped separator 366 * @return an array of strings 367 */ 368 public static String[] split(String str) { 369 return split(str, ESCAPE_CHAR, COMMA); 370 } 371 372 /** 373 * Split a string using the given separator 374 * @param str a string that may have escaped separator 375 * @param escapeChar a char that be used to escape the separator 376 * @param separator a separator char 377 * @return an array of strings 378 */ 379 public static String[] split( 380 String str, char escapeChar, char separator) { 381 if (str==null) { 382 return null; 383 } 384 ArrayList<String> strList = new ArrayList<String>(); 385 StringBuilder split = new StringBuilder(); 386 int index = 0; 387 while ((index = findNext(str, separator, escapeChar, index, split)) >= 0) { 388 ++index; // move over the separator for next search 389 strList.add(split.toString()); 390 split.setLength(0); // reset the buffer 391 } 392 strList.add(split.toString()); 393 // remove trailing empty split(s) 394 int last = strList.size(); // last split 395 while (--last>=0 && "".equals(strList.get(last))) { 396 strList.remove(last); 397 } 398 return strList.toArray(new String[strList.size()]); 399 } 400 401 /** 402 * Split a string using the given separator, with no escaping performed. 403 * @param str a string to be split. Note that this may not be null. 404 * @param separator a separator char 405 * @return an array of strings 406 */ 407 public static String[] split( 408 String str, char separator) { 409 // String.split returns a single empty result for splitting the empty 410 // string. 411 if ("".equals(str)) { 412 return new String[]{""}; 413 } 414 ArrayList<String> strList = new ArrayList<String>(); 415 int startIndex = 0; 416 int nextIndex = 0; 417 while ((nextIndex = str.indexOf((int)separator, startIndex)) != -1) { 418 strList.add(str.substring(startIndex, nextIndex)); 419 startIndex = nextIndex + 1; 420 } 421 strList.add(str.substring(startIndex)); 422 // remove trailing empty split(s) 423 int last = strList.size(); // last split 424 while (--last>=0 && "".equals(strList.get(last))) { 425 strList.remove(last); 426 } 427 return strList.toArray(new String[strList.size()]); 428 } 429 430 /** 431 * Finds the first occurrence of the separator character ignoring the escaped 432 * separators starting from the index. Note the substring between the index 433 * and the position of the separator is passed. 434 * @param str the source string 435 * @param separator the character to find 436 * @param escapeChar character used to escape 437 * @param start from where to search 438 * @param split used to pass back the extracted string 439 */ 440 public static int findNext(String str, char separator, char escapeChar, 441 int start, StringBuilder split) { 442 int numPreEscapes = 0; 443 for (int i = start; i < str.length(); i++) { 444 char curChar = str.charAt(i); 445 if (numPreEscapes == 0 && curChar == separator) { // separator 446 return i; 447 } else { 448 split.append(curChar); 449 numPreEscapes = (curChar == escapeChar) 450 ? (++numPreEscapes) % 2 451 : 0; 452 } 453 } 454 return -1; 455 } 456 457 /** 458 * Escape commas in the string using the default escape char 459 * @param str a string 460 * @return an escaped string 461 */ 462 public static String escapeString(String str) { 463 return escapeString(str, ESCAPE_CHAR, COMMA); 464 } 465 466 /** 467 * Escape <code>charToEscape</code> in the string 468 * with the escape char <code>escapeChar</code> 469 * 470 * @param str string 471 * @param escapeChar escape char 472 * @param charToEscape the char to be escaped 473 * @return an escaped string 474 */ 475 public static String escapeString( 476 String str, char escapeChar, char charToEscape) { 477 return escapeString(str, escapeChar, new char[] {charToEscape}); 478 } 479 480 // check if the character array has the character 481 private static boolean hasChar(char[] chars, char character) { 482 for (char target : chars) { 483 if (character == target) { 484 return true; 485 } 486 } 487 return false; 488 } 489 490 /** 491 * @param charsToEscape array of characters to be escaped 492 */ 493 public static String escapeString(String str, char escapeChar, 494 char[] charsToEscape) { 495 if (str == null) { 496 return null; 497 } 498 StringBuilder result = new StringBuilder(); 499 for (int i=0; i<str.length(); i++) { 500 char curChar = str.charAt(i); 501 if (curChar == escapeChar || hasChar(charsToEscape, curChar)) { 502 // special char 503 result.append(escapeChar); 504 } 505 result.append(curChar); 506 } 507 return result.toString(); 508 } 509 510 /** 511 * Unescape commas in the string using the default escape char 512 * @param str a string 513 * @return an unescaped string 514 */ 515 public static String unEscapeString(String str) { 516 return unEscapeString(str, ESCAPE_CHAR, COMMA); 517 } 518 519 /** 520 * Unescape <code>charToEscape</code> in the string 521 * with the escape char <code>escapeChar</code> 522 * 523 * @param str string 524 * @param escapeChar escape char 525 * @param charToEscape the escaped char 526 * @return an unescaped string 527 */ 528 public static String unEscapeString( 529 String str, char escapeChar, char charToEscape) { 530 return unEscapeString(str, escapeChar, new char[] {charToEscape}); 531 } 532 533 /** 534 * @param charsToEscape array of characters to unescape 535 */ 536 public static String unEscapeString(String str, char escapeChar, 537 char[] charsToEscape) { 538 if (str == null) { 539 return null; 540 } 541 StringBuilder result = new StringBuilder(str.length()); 542 boolean hasPreEscape = false; 543 for (int i=0; i<str.length(); i++) { 544 char curChar = str.charAt(i); 545 if (hasPreEscape) { 546 if (curChar != escapeChar && !hasChar(charsToEscape, curChar)) { 547 // no special char 548 throw new IllegalArgumentException("Illegal escaped string " + str + 549 " unescaped " + escapeChar + " at " + (i-1)); 550 } 551 // otherwise discard the escape char 552 result.append(curChar); 553 hasPreEscape = false; 554 } else { 555 if (hasChar(charsToEscape, curChar)) { 556 throw new IllegalArgumentException("Illegal escaped string " + str + 557 " unescaped " + curChar + " at " + i); 558 } else if (curChar == escapeChar) { 559 hasPreEscape = true; 560 } else { 561 result.append(curChar); 562 } 563 } 564 } 565 if (hasPreEscape ) { 566 throw new IllegalArgumentException("Illegal escaped string " + str + 567 ", not expecting " + escapeChar + " in the end." ); 568 } 569 return result.toString(); 570 } 571 572 /** 573 * Return a message for logging. 574 * @param prefix prefix keyword for the message 575 * @param msg content of the message 576 * @return a message for logging 577 */ 578 private static String toStartupShutdownString(String prefix, String [] msg) { 579 StringBuilder b = new StringBuilder(prefix); 580 b.append("\n/************************************************************"); 581 for(String s : msg) 582 b.append("\n" + prefix + s); 583 b.append("\n************************************************************/"); 584 return b.toString(); 585 } 586 587 /** 588 * Print a log message for starting up and shutting down 589 * @param clazz the class of the server 590 * @param args arguments 591 * @param LOG the target log object 592 */ 593 public static void startupShutdownMessage(Class<?> clazz, String[] args, 594 final org.apache.commons.logging.Log LOG) { 595 final String hostname = NetUtils.getHostname(); 596 final String classname = clazz.getSimpleName(); 597 LOG.info( 598 toStartupShutdownString("STARTUP_MSG: ", new String[] { 599 "Starting " + classname, 600 " host = " + hostname, 601 " args = " + Arrays.asList(args), 602 " version = " + VersionInfo.getVersion(), 603 " classpath = " + System.getProperty("java.class.path"), 604 " build = " + VersionInfo.getUrl() + " -r " 605 + VersionInfo.getRevision() 606 + "; compiled by '" + VersionInfo.getUser() 607 + "' on " + VersionInfo.getDate()} 608 ) 609 ); 610 611 ShutdownHookManager.get().addShutdownHook( 612 new Runnable() { 613 @Override 614 public void run() { 615 LOG.info(toStartupShutdownString("SHUTDOWN_MSG: ", new String[]{ 616 "Shutting down " + classname + " at " + hostname})); 617 } 618 }, SHUTDOWN_HOOK_PRIORITY); 619 620 } 621 622 /** 623 * The traditional binary prefixes, kilo, mega, ..., exa, 624 * which can be represented by a 64-bit integer. 625 * TraditionalBinaryPrefix symbol are case insensitive. 626 */ 627 public static enum TraditionalBinaryPrefix { 628 KILO(1024), 629 MEGA(KILO.value << 10), 630 GIGA(MEGA.value << 10), 631 TERA(GIGA.value << 10), 632 PETA(TERA.value << 10), 633 EXA(PETA.value << 10); 634 635 public final long value; 636 public final char symbol; 637 638 TraditionalBinaryPrefix(long value) { 639 this.value = value; 640 this.symbol = toString().charAt(0); 641 } 642 643 /** 644 * @return The TraditionalBinaryPrefix object corresponding to the symbol. 645 */ 646 public static TraditionalBinaryPrefix valueOf(char symbol) { 647 symbol = Character.toUpperCase(symbol); 648 for(TraditionalBinaryPrefix prefix : TraditionalBinaryPrefix.values()) { 649 if (symbol == prefix.symbol) { 650 return prefix; 651 } 652 } 653 throw new IllegalArgumentException("Unknown symbol '" + symbol + "'"); 654 } 655 656 /** 657 * Convert a string to long. 658 * The input string is first be trimmed 659 * and then it is parsed with traditional binary prefix. 660 * 661 * For example, 662 * "-1230k" will be converted to -1230 * 1024 = -1259520; 663 * "891g" will be converted to 891 * 1024^3 = 956703965184; 664 * 665 * @param s input string 666 * @return a long value represented by the input string. 667 */ 668 public static long string2long(String s) { 669 s = s.trim(); 670 final int lastpos = s.length() - 1; 671 final char lastchar = s.charAt(lastpos); 672 if (Character.isDigit(lastchar)) 673 return Long.parseLong(s); 674 else { 675 long prefix; 676 try { 677 prefix = TraditionalBinaryPrefix.valueOf(lastchar).value; 678 } catch (IllegalArgumentException e) { 679 throw new IllegalArgumentException("Invalid size prefix '" + lastchar 680 + "' in '" + s 681 + "'. Allowed prefixes are k, m, g, t, p, e(case insensitive)"); 682 } 683 long num = Long.parseLong(s.substring(0, lastpos)); 684 if (num > (Long.MAX_VALUE/prefix) || num < (Long.MIN_VALUE/prefix)) { 685 throw new IllegalArgumentException(s + " does not fit in a Long"); 686 } 687 return num * prefix; 688 } 689 } 690 } 691 692 /** 693 * Escapes HTML Special characters present in the string. 694 * @param string 695 * @return HTML Escaped String representation 696 */ 697 public static String escapeHTML(String string) { 698 if(string == null) { 699 return null; 700 } 701 StringBuilder sb = new StringBuilder(); 702 boolean lastCharacterWasSpace = false; 703 char[] chars = string.toCharArray(); 704 for(char c : chars) { 705 if(c == ' ') { 706 if(lastCharacterWasSpace){ 707 lastCharacterWasSpace = false; 708 sb.append(" "); 709 }else { 710 lastCharacterWasSpace=true; 711 sb.append(" "); 712 } 713 }else { 714 lastCharacterWasSpace = false; 715 switch(c) { 716 case '<': sb.append("<"); break; 717 case '>': sb.append(">"); break; 718 case '&': sb.append("&"); break; 719 case '"': sb.append("""); break; 720 default : sb.append(c);break; 721 } 722 } 723 } 724 725 return sb.toString(); 726 } 727 728 /** 729 * Return an abbreviated English-language desc of the byte length 730 */ 731 public static String byteDesc(long len) { 732 double val = 0.0; 733 String ending = ""; 734 if (len < 1024 * 1024) { 735 val = (1.0 * len) / 1024; 736 ending = " KB"; 737 } else if (len < 1024 * 1024 * 1024) { 738 val = (1.0 * len) / (1024 * 1024); 739 ending = " MB"; 740 } else if (len < 1024L * 1024 * 1024 * 1024) { 741 val = (1.0 * len) / (1024 * 1024 * 1024); 742 ending = " GB"; 743 } else if (len < 1024L * 1024 * 1024 * 1024 * 1024) { 744 val = (1.0 * len) / (1024L * 1024 * 1024 * 1024); 745 ending = " TB"; 746 } else { 747 val = (1.0 * len) / (1024L * 1024 * 1024 * 1024 * 1024); 748 ending = " PB"; 749 } 750 return limitDecimalTo2(val) + ending; 751 } 752 753 public static synchronized String limitDecimalTo2(double d) { 754 return decimalFormat.format(d); 755 } 756 757 /** 758 * Concatenates strings, using a separator. 759 * 760 * @param separator Separator to join with. 761 * @param strings Strings to join. 762 */ 763 public static String join(CharSequence separator, Iterable<?> strings) { 764 Iterator<?> i = strings.iterator(); 765 if (!i.hasNext()) { 766 return ""; 767 } 768 StringBuilder sb = new StringBuilder(i.next().toString()); 769 while (i.hasNext()) { 770 sb.append(separator); 771 sb.append(i.next().toString()); 772 } 773 return sb.toString(); 774 } 775 776 /** 777 * Convert SOME_STUFF to SomeStuff 778 * 779 * @param s input string 780 * @return camelized string 781 */ 782 public static String camelize(String s) { 783 StringBuilder sb = new StringBuilder(); 784 String[] words = split(s.toLowerCase(Locale.US), ESCAPE_CHAR, '_'); 785 786 for (String word : words) 787 sb.append(org.apache.commons.lang.StringUtils.capitalize(word)); 788 789 return sb.toString(); 790 } 791 }