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