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.util.ArrayList; 027 import java.util.Arrays; 028 import java.util.Collection; 029 import java.util.Date; 030 import java.util.Iterator; 031 import java.util.List; 032 import java.util.Locale; 033 import java.util.Map; 034 import java.util.StringTokenizer; 035 import java.util.regex.Matcher; 036 import java.util.regex.Pattern; 037 038 import org.apache.commons.lang.SystemUtils; 039 import org.apache.hadoop.classification.InterfaceAudience; 040 import org.apache.hadoop.classification.InterfaceStability; 041 import org.apache.hadoop.fs.Path; 042 import org.apache.hadoop.net.NetUtils; 043 044 import com.google.common.net.InetAddresses; 045 046 /** 047 * General string utils 048 */ 049 @InterfaceAudience.Private 050 @InterfaceStability.Unstable 051 public class StringUtils { 052 053 /** 054 * Priority of the StringUtils shutdown hook. 055 */ 056 public static final int SHUTDOWN_HOOK_PRIORITY = 0; 057 058 /** 059 * Shell environment variables: $ followed by one letter or _ followed by 060 * multiple letters, numbers, or underscores. The group captures the 061 * environment variable name without the leading $. 062 */ 063 public static final Pattern SHELL_ENV_VAR_PATTERN = 064 Pattern.compile("\\$([A-Za-z_]{1}[A-Za-z0-9_]*)"); 065 066 /** 067 * Windows environment variables: surrounded by %. The group captures the 068 * environment variable name without the leading and trailing %. 069 */ 070 public static final Pattern WIN_ENV_VAR_PATTERN = Pattern.compile("%(.*?)%"); 071 072 /** 073 * Regular expression that matches and captures environment variable names 074 * according to platform-specific rules. 075 */ 076 public static final Pattern ENV_VAR_PATTERN = Shell.WINDOWS ? 077 WIN_ENV_VAR_PATTERN : SHELL_ENV_VAR_PATTERN; 078 079 /** 080 * Make a string representation of the exception. 081 * @param e The exception to stringify 082 * @return A string with exception name and call stack. 083 */ 084 public static String stringifyException(Throwable e) { 085 StringWriter stm = new StringWriter(); 086 PrintWriter wrt = new PrintWriter(stm); 087 e.printStackTrace(wrt); 088 wrt.close(); 089 return stm.toString(); 090 } 091 092 /** 093 * Given a full hostname, return the word upto the first dot. 094 * @param fullHostname the full hostname 095 * @return the hostname to the first dot 096 */ 097 public static String simpleHostname(String fullHostname) { 098 if (InetAddresses.isInetAddress(fullHostname)) { 099 return fullHostname; 100 } 101 int offset = fullHostname.indexOf('.'); 102 if (offset != -1) { 103 return fullHostname.substring(0, offset); 104 } 105 return fullHostname; 106 } 107 108 /** 109 * Given an integer, return a string that is in an approximate, but human 110 * readable format. 111 * @param number the number to format 112 * @return a human readable form of the integer 113 * 114 * @deprecated use {@link TraditionalBinaryPrefix#long2String(long, String, int)}. 115 */ 116 @Deprecated 117 public static String humanReadableInt(long number) { 118 return TraditionalBinaryPrefix.long2String(number, "", 1); 119 } 120 121 /** The same as String.format(Locale.ENGLISH, format, objects). */ 122 public static String format(final String format, final Object... objects) { 123 return String.format(Locale.ENGLISH, format, objects); 124 } 125 126 /** 127 * Format a percentage for presentation to the user. 128 * @param fraction the percentage as a fraction, e.g. 0.1 = 10% 129 * @param decimalPlaces the number of decimal places 130 * @return a string representation of the percentage 131 */ 132 public static String formatPercent(double fraction, int decimalPlaces) { 133 return format("%." + decimalPlaces + "f%%", fraction*100); 134 } 135 136 /** 137 * Given an array of strings, return a comma-separated list of its elements. 138 * @param strs Array of strings 139 * @return Empty string if strs.length is 0, comma separated list of strings 140 * otherwise 141 */ 142 143 public static String arrayToString(String[] strs) { 144 if (strs.length == 0) { return ""; } 145 StringBuilder sbuf = new StringBuilder(); 146 sbuf.append(strs[0]); 147 for (int idx = 1; idx < strs.length; idx++) { 148 sbuf.append(","); 149 sbuf.append(strs[idx]); 150 } 151 return sbuf.toString(); 152 } 153 154 /** 155 * Given an array of bytes it will convert the bytes to a hex string 156 * representation of the bytes 157 * @param bytes 158 * @param start start index, inclusively 159 * @param end end index, exclusively 160 * @return hex string representation of the byte array 161 */ 162 public static String byteToHexString(byte[] bytes, int start, int end) { 163 if (bytes == null) { 164 throw new IllegalArgumentException("bytes == null"); 165 } 166 StringBuilder s = new StringBuilder(); 167 for(int i = start; i < end; i++) { 168 s.append(format("%02x", bytes[i])); 169 } 170 return s.toString(); 171 } 172 173 /** Same as byteToHexString(bytes, 0, bytes.length). */ 174 public static String byteToHexString(byte bytes[]) { 175 return byteToHexString(bytes, 0, bytes.length); 176 } 177 178 /** 179 * Given a hexstring this will return the byte array corresponding to the 180 * string 181 * @param hex the hex String array 182 * @return a byte array that is a hex string representation of the given 183 * string. The size of the byte array is therefore hex.length/2 184 */ 185 public static byte[] hexStringToByte(String hex) { 186 byte[] bts = new byte[hex.length() / 2]; 187 for (int i = 0; i < bts.length; i++) { 188 bts[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16); 189 } 190 return bts; 191 } 192 /** 193 * 194 * @param uris 195 */ 196 public static String uriToString(URI[] uris){ 197 if (uris == null) { 198 return null; 199 } 200 StringBuilder ret = new StringBuilder(uris[0].toString()); 201 for(int i = 1; i < uris.length;i++){ 202 ret.append(","); 203 ret.append(uris[i].toString()); 204 } 205 return ret.toString(); 206 } 207 208 /** 209 * @param str 210 * The string array to be parsed into an URI array. 211 * @return <tt>null</tt> if str is <tt>null</tt>, else the URI array 212 * equivalent to str. 213 * @throws IllegalArgumentException 214 * If any string in str violates RFC 2396. 215 */ 216 public static URI[] stringToURI(String[] str){ 217 if (str == null) 218 return null; 219 URI[] uris = new URI[str.length]; 220 for (int i = 0; i < str.length;i++){ 221 try{ 222 uris[i] = new URI(str[i]); 223 }catch(URISyntaxException ur){ 224 throw new IllegalArgumentException( 225 "Failed to create uri for " + str[i], ur); 226 } 227 } 228 return uris; 229 } 230 231 /** 232 * 233 * @param str 234 */ 235 public static Path[] stringToPath(String[] str){ 236 if (str == null) { 237 return null; 238 } 239 Path[] p = new Path[str.length]; 240 for (int i = 0; i < str.length;i++){ 241 p[i] = new Path(str[i]); 242 } 243 return p; 244 } 245 /** 246 * 247 * Given a finish and start time in long milliseconds, returns a 248 * String in the format Xhrs, Ymins, Z sec, for the time difference between two times. 249 * If finish time comes before start time then negative valeus of X, Y and Z wil return. 250 * 251 * @param finishTime finish time 252 * @param startTime start time 253 */ 254 public static String formatTimeDiff(long finishTime, long startTime){ 255 long timeDiff = finishTime - startTime; 256 return formatTime(timeDiff); 257 } 258 259 /** 260 * 261 * Given the time in long milliseconds, returns a 262 * String in the format Xhrs, Ymins, Z sec. 263 * 264 * @param timeDiff The time difference to format 265 */ 266 public static String formatTime(long timeDiff){ 267 StringBuilder buf = new StringBuilder(); 268 long hours = timeDiff / (60*60*1000); 269 long rem = (timeDiff % (60*60*1000)); 270 long minutes = rem / (60*1000); 271 rem = rem % (60*1000); 272 long seconds = rem / 1000; 273 274 if (hours != 0){ 275 buf.append(hours); 276 buf.append("hrs, "); 277 } 278 if (minutes != 0){ 279 buf.append(minutes); 280 buf.append("mins, "); 281 } 282 // return "0sec if no difference 283 buf.append(seconds); 284 buf.append("sec"); 285 return buf.toString(); 286 } 287 /** 288 * Formats time in ms and appends difference (finishTime - startTime) 289 * as returned by formatTimeDiff(). 290 * If finish time is 0, empty string is returned, if start time is 0 291 * then difference is not appended to return value. 292 * @param dateFormat date format to use 293 * @param finishTime fnish time 294 * @param startTime start time 295 * @return formatted value. 296 */ 297 public static String getFormattedTimeWithDiff(DateFormat dateFormat, 298 long finishTime, long startTime){ 299 StringBuilder buf = new StringBuilder(); 300 if (0 != finishTime) { 301 buf.append(dateFormat.format(new Date(finishTime))); 302 if (0 != startTime){ 303 buf.append(" (" + formatTimeDiff(finishTime , startTime) + ")"); 304 } 305 } 306 return buf.toString(); 307 } 308 309 /** 310 * Returns an arraylist of strings. 311 * @param str the comma seperated string values 312 * @return the arraylist of the comma seperated string values 313 */ 314 public static String[] getStrings(String str){ 315 Collection<String> values = getStringCollection(str); 316 if(values.size() == 0) { 317 return null; 318 } 319 return values.toArray(new String[values.size()]); 320 } 321 322 /** 323 * Returns a collection of strings. 324 * @param str comma seperated string values 325 * @return an <code>ArrayList</code> of string values 326 */ 327 public static Collection<String> getStringCollection(String str){ 328 String delim = ","; 329 return getStringCollection(str, delim); 330 } 331 332 /** 333 * Returns a collection of strings. 334 * 335 * @param str 336 * String to parse 337 * @param delim 338 * delimiter to separate the values 339 * @return Collection of parsed elements. 340 */ 341 public static Collection<String> getStringCollection(String str, String delim) { 342 List<String> values = new ArrayList<String>(); 343 if (str == null) 344 return values; 345 StringTokenizer tokenizer = new StringTokenizer(str, delim); 346 while (tokenizer.hasMoreTokens()) { 347 values.add(tokenizer.nextToken()); 348 } 349 return values; 350 } 351 352 /** 353 * Splits a comma separated value <code>String</code>, trimming leading and trailing whitespace on each value. 354 * @param str a comma separated <String> with values 355 * @return a <code>Collection</code> of <code>String</code> values 356 */ 357 public static Collection<String> getTrimmedStringCollection(String str){ 358 return new ArrayList<String>( 359 Arrays.asList(getTrimmedStrings(str))); 360 } 361 362 /** 363 * Splits a comma separated value <code>String</code>, trimming leading and trailing whitespace on each value. 364 * @param str a comma separated <String> with values 365 * @return an array of <code>String</code> values 366 */ 367 public static String[] getTrimmedStrings(String str){ 368 if (null == str || str.trim().isEmpty()) { 369 return emptyStringArray; 370 } 371 372 return str.trim().split("\\s*,\\s*"); 373 } 374 375 final public static String[] emptyStringArray = {}; 376 final public static char COMMA = ','; 377 final public static String COMMA_STR = ","; 378 final public static char ESCAPE_CHAR = '\\'; 379 380 /** 381 * Split a string using the default separator 382 * @param str a string that may have escaped separator 383 * @return an array of strings 384 */ 385 public static String[] split(String str) { 386 return split(str, ESCAPE_CHAR, COMMA); 387 } 388 389 /** 390 * Split a string using the given separator 391 * @param str a string that may have escaped separator 392 * @param escapeChar a char that be used to escape the separator 393 * @param separator a separator char 394 * @return an array of strings 395 */ 396 public static String[] split( 397 String str, char escapeChar, char separator) { 398 if (str==null) { 399 return null; 400 } 401 ArrayList<String> strList = new ArrayList<String>(); 402 StringBuilder split = new StringBuilder(); 403 int index = 0; 404 while ((index = findNext(str, separator, escapeChar, index, split)) >= 0) { 405 ++index; // move over the separator for next search 406 strList.add(split.toString()); 407 split.setLength(0); // reset the buffer 408 } 409 strList.add(split.toString()); 410 // remove trailing empty split(s) 411 int last = strList.size(); // last split 412 while (--last>=0 && "".equals(strList.get(last))) { 413 strList.remove(last); 414 } 415 return strList.toArray(new String[strList.size()]); 416 } 417 418 /** 419 * Split a string using the given separator, with no escaping performed. 420 * @param str a string to be split. Note that this may not be null. 421 * @param separator a separator char 422 * @return an array of strings 423 */ 424 public static String[] split( 425 String str, char separator) { 426 // String.split returns a single empty result for splitting the empty 427 // string. 428 if (str.isEmpty()) { 429 return new String[]{""}; 430 } 431 ArrayList<String> strList = new ArrayList<String>(); 432 int startIndex = 0; 433 int nextIndex = 0; 434 while ((nextIndex = str.indexOf(separator, startIndex)) != -1) { 435 strList.add(str.substring(startIndex, nextIndex)); 436 startIndex = nextIndex + 1; 437 } 438 strList.add(str.substring(startIndex)); 439 // remove trailing empty split(s) 440 int last = strList.size(); // last split 441 while (--last>=0 && "".equals(strList.get(last))) { 442 strList.remove(last); 443 } 444 return strList.toArray(new String[strList.size()]); 445 } 446 447 /** 448 * Finds the first occurrence of the separator character ignoring the escaped 449 * separators starting from the index. Note the substring between the index 450 * and the position of the separator is passed. 451 * @param str the source string 452 * @param separator the character to find 453 * @param escapeChar character used to escape 454 * @param start from where to search 455 * @param split used to pass back the extracted string 456 */ 457 public static int findNext(String str, char separator, char escapeChar, 458 int start, StringBuilder split) { 459 int numPreEscapes = 0; 460 for (int i = start; i < str.length(); i++) { 461 char curChar = str.charAt(i); 462 if (numPreEscapes == 0 && curChar == separator) { // separator 463 return i; 464 } else { 465 split.append(curChar); 466 numPreEscapes = (curChar == escapeChar) 467 ? (++numPreEscapes) % 2 468 : 0; 469 } 470 } 471 return -1; 472 } 473 474 /** 475 * Escape commas in the string using the default escape char 476 * @param str a string 477 * @return an escaped string 478 */ 479 public static String escapeString(String str) { 480 return escapeString(str, ESCAPE_CHAR, COMMA); 481 } 482 483 /** 484 * Escape <code>charToEscape</code> in the string 485 * with the escape char <code>escapeChar</code> 486 * 487 * @param str string 488 * @param escapeChar escape char 489 * @param charToEscape the char to be escaped 490 * @return an escaped string 491 */ 492 public static String escapeString( 493 String str, char escapeChar, char charToEscape) { 494 return escapeString(str, escapeChar, new char[] {charToEscape}); 495 } 496 497 // check if the character array has the character 498 private static boolean hasChar(char[] chars, char character) { 499 for (char target : chars) { 500 if (character == target) { 501 return true; 502 } 503 } 504 return false; 505 } 506 507 /** 508 * @param charsToEscape array of characters to be escaped 509 */ 510 public static String escapeString(String str, char escapeChar, 511 char[] charsToEscape) { 512 if (str == null) { 513 return null; 514 } 515 StringBuilder result = new StringBuilder(); 516 for (int i=0; i<str.length(); i++) { 517 char curChar = str.charAt(i); 518 if (curChar == escapeChar || hasChar(charsToEscape, curChar)) { 519 // special char 520 result.append(escapeChar); 521 } 522 result.append(curChar); 523 } 524 return result.toString(); 525 } 526 527 /** 528 * Unescape commas in the string using the default escape char 529 * @param str a string 530 * @return an unescaped string 531 */ 532 public static String unEscapeString(String str) { 533 return unEscapeString(str, ESCAPE_CHAR, COMMA); 534 } 535 536 /** 537 * Unescape <code>charToEscape</code> in the string 538 * with the escape char <code>escapeChar</code> 539 * 540 * @param str string 541 * @param escapeChar escape char 542 * @param charToEscape the escaped char 543 * @return an unescaped string 544 */ 545 public static String unEscapeString( 546 String str, char escapeChar, char charToEscape) { 547 return unEscapeString(str, escapeChar, new char[] {charToEscape}); 548 } 549 550 /** 551 * @param charsToEscape array of characters to unescape 552 */ 553 public static String unEscapeString(String str, char escapeChar, 554 char[] charsToEscape) { 555 if (str == null) { 556 return null; 557 } 558 StringBuilder result = new StringBuilder(str.length()); 559 boolean hasPreEscape = false; 560 for (int i=0; i<str.length(); i++) { 561 char curChar = str.charAt(i); 562 if (hasPreEscape) { 563 if (curChar != escapeChar && !hasChar(charsToEscape, curChar)) { 564 // no special char 565 throw new IllegalArgumentException("Illegal escaped string " + str + 566 " unescaped " + escapeChar + " at " + (i-1)); 567 } 568 // otherwise discard the escape char 569 result.append(curChar); 570 hasPreEscape = false; 571 } else { 572 if (hasChar(charsToEscape, curChar)) { 573 throw new IllegalArgumentException("Illegal escaped string " + str + 574 " unescaped " + curChar + " at " + i); 575 } else if (curChar == escapeChar) { 576 hasPreEscape = true; 577 } else { 578 result.append(curChar); 579 } 580 } 581 } 582 if (hasPreEscape ) { 583 throw new IllegalArgumentException("Illegal escaped string " + str + 584 ", not expecting " + escapeChar + " in the end." ); 585 } 586 return result.toString(); 587 } 588 589 /** 590 * Return a message for logging. 591 * @param prefix prefix keyword for the message 592 * @param msg content of the message 593 * @return a message for logging 594 */ 595 private static String toStartupShutdownString(String prefix, String [] msg) { 596 StringBuilder b = new StringBuilder(prefix); 597 b.append("\n/************************************************************"); 598 for(String s : msg) 599 b.append("\n" + prefix + s); 600 b.append("\n************************************************************/"); 601 return b.toString(); 602 } 603 604 /** 605 * Print a log message for starting up and shutting down 606 * @param clazz the class of the server 607 * @param args arguments 608 * @param LOG the target log object 609 */ 610 public static void startupShutdownMessage(Class<?> clazz, String[] args, 611 final org.apache.commons.logging.Log LOG) { 612 final String hostname = NetUtils.getHostname(); 613 final String classname = clazz.getSimpleName(); 614 LOG.info( 615 toStartupShutdownString("STARTUP_MSG: ", new String[] { 616 "Starting " + classname, 617 " host = " + hostname, 618 " args = " + Arrays.asList(args), 619 " version = " + VersionInfo.getVersion(), 620 " classpath = " + System.getProperty("java.class.path"), 621 " build = " + VersionInfo.getUrl() + " -r " 622 + VersionInfo.getRevision() 623 + "; compiled by '" + VersionInfo.getUser() 624 + "' on " + VersionInfo.getDate(), 625 " java = " + System.getProperty("java.version") } 626 ) 627 ); 628 629 if (SystemUtils.IS_OS_UNIX) { 630 try { 631 SignalLogger.INSTANCE.register(LOG); 632 } catch (Throwable t) { 633 LOG.warn("failed to register any UNIX signal loggers: ", t); 634 } 635 } 636 ShutdownHookManager.get().addShutdownHook( 637 new Runnable() { 638 @Override 639 public void run() { 640 LOG.info(toStartupShutdownString("SHUTDOWN_MSG: ", new String[]{ 641 "Shutting down " + classname + " at " + hostname})); 642 } 643 }, SHUTDOWN_HOOK_PRIORITY); 644 645 } 646 647 /** 648 * The traditional binary prefixes, kilo, mega, ..., exa, 649 * which can be represented by a 64-bit integer. 650 * TraditionalBinaryPrefix symbol are case insensitive. 651 */ 652 public static enum TraditionalBinaryPrefix { 653 KILO(10), 654 MEGA(KILO.bitShift + 10), 655 GIGA(MEGA.bitShift + 10), 656 TERA(GIGA.bitShift + 10), 657 PETA(TERA.bitShift + 10), 658 EXA (PETA.bitShift + 10); 659 660 public final long value; 661 public final char symbol; 662 public final int bitShift; 663 public final long bitMask; 664 665 private TraditionalBinaryPrefix(int bitShift) { 666 this.bitShift = bitShift; 667 this.value = 1L << bitShift; 668 this.bitMask = this.value - 1L; 669 this.symbol = toString().charAt(0); 670 } 671 672 /** 673 * @return The TraditionalBinaryPrefix object corresponding to the symbol. 674 */ 675 public static TraditionalBinaryPrefix valueOf(char symbol) { 676 symbol = Character.toUpperCase(symbol); 677 for(TraditionalBinaryPrefix prefix : TraditionalBinaryPrefix.values()) { 678 if (symbol == prefix.symbol) { 679 return prefix; 680 } 681 } 682 throw new IllegalArgumentException("Unknown symbol '" + symbol + "'"); 683 } 684 685 /** 686 * Convert a string to long. 687 * The input string is first be trimmed 688 * and then it is parsed with traditional binary prefix. 689 * 690 * For example, 691 * "-1230k" will be converted to -1230 * 1024 = -1259520; 692 * "891g" will be converted to 891 * 1024^3 = 956703965184; 693 * 694 * @param s input string 695 * @return a long value represented by the input string. 696 */ 697 public static long string2long(String s) { 698 s = s.trim(); 699 final int lastpos = s.length() - 1; 700 final char lastchar = s.charAt(lastpos); 701 if (Character.isDigit(lastchar)) 702 return Long.parseLong(s); 703 else { 704 long prefix; 705 try { 706 prefix = TraditionalBinaryPrefix.valueOf(lastchar).value; 707 } catch (IllegalArgumentException e) { 708 throw new IllegalArgumentException("Invalid size prefix '" + lastchar 709 + "' in '" + s 710 + "'. Allowed prefixes are k, m, g, t, p, e(case insensitive)"); 711 } 712 long num = Long.parseLong(s.substring(0, lastpos)); 713 if (num > (Long.MAX_VALUE/prefix) || num < (Long.MIN_VALUE/prefix)) { 714 throw new IllegalArgumentException(s + " does not fit in a Long"); 715 } 716 return num * prefix; 717 } 718 } 719 720 /** 721 * Convert a long integer to a string with traditional binary prefix. 722 * 723 * @param n the value to be converted 724 * @param unit The unit, e.g. "B" for bytes. 725 * @param decimalPlaces The number of decimal places. 726 * @return a string with traditional binary prefix. 727 */ 728 public static String long2String(long n, String unit, int decimalPlaces) { 729 if (unit == null) { 730 unit = ""; 731 } 732 //take care a special case 733 if (n == Long.MIN_VALUE) { 734 return "-8 " + EXA.symbol + unit; 735 } 736 737 final StringBuilder b = new StringBuilder(); 738 //take care negative numbers 739 if (n < 0) { 740 b.append('-'); 741 n = -n; 742 } 743 if (n < KILO.value) { 744 //no prefix 745 b.append(n); 746 return (unit.isEmpty()? b: b.append(" ").append(unit)).toString(); 747 } else { 748 //find traditional binary prefix 749 int i = 0; 750 for(; i < values().length && n >= values()[i].value; i++); 751 TraditionalBinaryPrefix prefix = values()[i - 1]; 752 753 if ((n & prefix.bitMask) == 0) { 754 //exact division 755 b.append(n >> prefix.bitShift); 756 } else { 757 final String format = "%." + decimalPlaces + "f"; 758 String s = format(format, n/(double)prefix.value); 759 //check a special rounding up case 760 if (s.startsWith("1024")) { 761 prefix = values()[i]; 762 s = format(format, n/(double)prefix.value); 763 } 764 b.append(s); 765 } 766 return b.append(' ').append(prefix.symbol).append(unit).toString(); 767 } 768 } 769 } 770 771 /** 772 * Escapes HTML Special characters present in the string. 773 * @param string 774 * @return HTML Escaped String representation 775 */ 776 public static String escapeHTML(String string) { 777 if(string == null) { 778 return null; 779 } 780 StringBuilder sb = new StringBuilder(); 781 boolean lastCharacterWasSpace = false; 782 char[] chars = string.toCharArray(); 783 for(char c : chars) { 784 if(c == ' ') { 785 if(lastCharacterWasSpace){ 786 lastCharacterWasSpace = false; 787 sb.append(" "); 788 }else { 789 lastCharacterWasSpace=true; 790 sb.append(" "); 791 } 792 }else { 793 lastCharacterWasSpace = false; 794 switch(c) { 795 case '<': sb.append("<"); break; 796 case '>': sb.append(">"); break; 797 case '&': sb.append("&"); break; 798 case '"': sb.append("""); break; 799 default : sb.append(c);break; 800 } 801 } 802 } 803 804 return sb.toString(); 805 } 806 807 /** 808 * @return a byte description of the given long interger value. 809 */ 810 public static String byteDesc(long len) { 811 return TraditionalBinaryPrefix.long2String(len, "B", 2); 812 } 813 814 /** @deprecated use StringUtils.format("%.2f", d). */ 815 @Deprecated 816 public static String limitDecimalTo2(double d) { 817 return format("%.2f", d); 818 } 819 820 /** 821 * Concatenates strings, using a separator. 822 * 823 * @param separator Separator to join with. 824 * @param strings Strings to join. 825 */ 826 public static String join(CharSequence separator, Iterable<?> strings) { 827 Iterator<?> i = strings.iterator(); 828 if (!i.hasNext()) { 829 return ""; 830 } 831 StringBuilder sb = new StringBuilder(i.next().toString()); 832 while (i.hasNext()) { 833 sb.append(separator); 834 sb.append(i.next().toString()); 835 } 836 return sb.toString(); 837 } 838 839 /** 840 * Concatenates strings, using a separator. 841 * 842 * @param separator to join with 843 * @param strings to join 844 * @return the joined string 845 */ 846 public static String join(CharSequence separator, String[] strings) { 847 // Ideally we don't have to duplicate the code here if array is iterable. 848 StringBuilder sb = new StringBuilder(); 849 boolean first = true; 850 for (String s : strings) { 851 if (first) { 852 first = false; 853 } else { 854 sb.append(separator); 855 } 856 sb.append(s); 857 } 858 return sb.toString(); 859 } 860 861 /** 862 * Convert SOME_STUFF to SomeStuff 863 * 864 * @param s input string 865 * @return camelized string 866 */ 867 public static String camelize(String s) { 868 StringBuilder sb = new StringBuilder(); 869 String[] words = split(s.toLowerCase(Locale.US), ESCAPE_CHAR, '_'); 870 871 for (String word : words) 872 sb.append(org.apache.commons.lang.StringUtils.capitalize(word)); 873 874 return sb.toString(); 875 } 876 877 /** 878 * Matches a template string against a pattern, replaces matched tokens with 879 * the supplied replacements, and returns the result. The regular expression 880 * must use a capturing group. The value of the first capturing group is used 881 * to look up the replacement. If no replacement is found for the token, then 882 * it is replaced with the empty string. 883 * 884 * For example, assume template is "%foo%_%bar%_%baz%", pattern is "%(.*?)%", 885 * and replacements contains 2 entries, mapping "foo" to "zoo" and "baz" to 886 * "zaz". The result returned would be "zoo__zaz". 887 * 888 * @param template String template to receive replacements 889 * @param pattern Pattern to match for identifying tokens, must use a capturing 890 * group 891 * @param replacements Map<String, String> mapping tokens identified by the 892 * capturing group to their replacement values 893 * @return String template with replacements 894 */ 895 public static String replaceTokens(String template, Pattern pattern, 896 Map<String, String> replacements) { 897 StringBuffer sb = new StringBuffer(); 898 Matcher matcher = pattern.matcher(template); 899 while (matcher.find()) { 900 String replacement = replacements.get(matcher.group(1)); 901 if (replacement == null) { 902 replacement = ""; 903 } 904 matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement)); 905 } 906 matcher.appendTail(sb); 907 return sb.toString(); 908 } 909 910 /** 911 * Get stack trace for a given thread. 912 */ 913 public static String getStackTrace(Thread t) { 914 final StackTraceElement[] stackTrace = t.getStackTrace(); 915 StringBuilder str = new StringBuilder(); 916 for (StackTraceElement e : stackTrace) { 917 str.append(e.toString() + "\n"); 918 } 919 return str.toString(); 920 } 921 922 /** 923 * From a list of command-line arguments, remove both an option and the 924 * next argument. 925 * 926 * @param name Name of the option to remove. Example: -foo. 927 * @param args List of arguments. 928 * @return null if the option was not found; the value of the 929 * option otherwise. 930 * @throws IllegalArgumentException if the option's argument is not present 931 */ 932 public static String popOptionWithArgument(String name, List<String> args) 933 throws IllegalArgumentException { 934 String val = null; 935 for (Iterator<String> iter = args.iterator(); iter.hasNext(); ) { 936 String cur = iter.next(); 937 if (cur.equals("--")) { 938 // stop parsing arguments when you see -- 939 break; 940 } else if (cur.equals(name)) { 941 iter.remove(); 942 if (!iter.hasNext()) { 943 throw new IllegalArgumentException("option " + name + " requires 1 " + 944 "argument."); 945 } 946 val = iter.next(); 947 iter.remove(); 948 break; 949 } 950 } 951 return val; 952 } 953 954 /** 955 * From a list of command-line arguments, remove an option. 956 * 957 * @param name Name of the option to remove. Example: -foo. 958 * @param args List of arguments. 959 * @return true if the option was found and removed; false otherwise. 960 */ 961 public static boolean popOption(String name, List<String> args) { 962 for (Iterator<String> iter = args.iterator(); iter.hasNext(); ) { 963 String cur = iter.next(); 964 if (cur.equals("--")) { 965 // stop parsing arguments when you see -- 966 break; 967 } else if (cur.equals(name)) { 968 iter.remove(); 969 return true; 970 } 971 } 972 return false; 973 } 974 975 /** 976 * From a list of command-line arguments, return the first non-option 977 * argument. Non-option arguments are those which either come after 978 * a double dash (--) or do not start with a dash. 979 * 980 * @param args List of arguments. 981 * @return The first non-option argument, or null if there were none. 982 */ 983 public static String popFirstNonOption(List<String> args) { 984 for (Iterator<String> iter = args.iterator(); iter.hasNext(); ) { 985 String cur = iter.next(); 986 if (cur.equals("--")) { 987 if (!iter.hasNext()) { 988 return null; 989 } 990 cur = iter.next(); 991 iter.remove(); 992 return cur; 993 } else if (!cur.startsWith("-")) { 994 iter.remove(); 995 return cur; 996 } 997 } 998 return null; 999 } 1000 }