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