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