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.Iterator;
030import java.util.LinkedHashSet;
031import java.util.List;
032import java.util.Locale;
033import java.util.Map;
034import java.util.Set;
035import java.util.StringTokenizer;
036import java.util.regex.Matcher;
037import java.util.regex.Pattern;
038
039import org.apache.commons.lang.SystemUtils;
040import org.apache.commons.lang.time.FastDateFormat;
041import org.apache.hadoop.classification.InterfaceAudience;
042import org.apache.hadoop.classification.InterfaceStability;
043import org.apache.hadoop.fs.Path;
044import org.apache.hadoop.net.NetUtils;
045
046import com.google.common.base.Preconditions;
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  /**
292   *
293   * Given the time in long milliseconds, returns a String in the sortable
294   * format Xhrs, Ymins, Zsec. X, Y, and Z are always two-digit. If the time is
295   * more than 100 hours ,it is displayed as 99hrs, 59mins, 59sec.
296   *
297   * @param timeDiff The time difference to format
298   */
299  public static String formatTimeSortable(long timeDiff) {
300    StringBuilder buf = new StringBuilder();
301    long hours = timeDiff / (60 * 60 * 1000);
302    long rem = (timeDiff % (60 * 60 * 1000));
303    long minutes = rem / (60 * 1000);
304    rem = rem % (60 * 1000);
305    long seconds = rem / 1000;
306
307    // if hours is more than 99 hours, it will be set a max value format
308    if (hours > 99) {
309      hours = 99;
310      minutes = 59;
311      seconds = 59;
312    }
313
314    buf.append(String.format("%02d", hours));
315    buf.append("hrs, ");
316
317    buf.append(String.format("%02d", minutes));
318    buf.append("mins, ");
319
320    buf.append(String.format("%02d", seconds));
321    buf.append("sec");
322    return buf.toString();
323  }
324
325  /**
326   * @param dateFormat date format to use
327   * @param finishTime finish time
328   * @param startTime  start time
329   * @return formatted value.
330   * Formats time in ms and appends difference (finishTime - startTime)
331   * as returned by formatTimeDiff().
332   * If finish time is 0, empty string is returned, if start time is 0
333   * then difference is not appended to return value.
334   * @deprecated Use
335   * {@link StringUtils#getFormattedTimeWithDiff(FastDateFormat, long, long)} or
336   * {@link StringUtils#getFormattedTimeWithDiff(String, long, long)} instead.
337   */
338  @Deprecated
339  public static String getFormattedTimeWithDiff(DateFormat dateFormat,
340      long finishTime, long startTime){
341    String formattedFinishTime = dateFormat.format(finishTime);
342    return getFormattedTimeWithDiff(formattedFinishTime, finishTime, startTime);
343  }
344
345  /**
346   * Formats time in ms and appends difference (finishTime - startTime)
347   * as returned by formatTimeDiff().
348   * If finish time is 0, empty string is returned, if start time is 0
349   * then difference is not appended to return value.
350   *
351   * @param dateFormat date format to use
352   * @param finishTime finish time
353   * @param startTime  start time
354   * @return formatted value.
355   */
356  public static String getFormattedTimeWithDiff(FastDateFormat dateFormat,
357      long finishTime, long startTime) {
358    String formattedFinishTime = dateFormat.format(finishTime);
359    return getFormattedTimeWithDiff(formattedFinishTime, finishTime, startTime);
360  }
361  /**
362   * Formats time in ms and appends difference (finishTime - startTime)
363   * as returned by formatTimeDiff().
364   * If finish time is 0, empty string is returned, if start time is 0
365   * then difference is not appended to return value.
366   * @param formattedFinishTime formattedFinishTime to use
367   * @param finishTime finish time
368   * @param startTime start time
369   * @return formatted value.
370   */
371  public static String getFormattedTimeWithDiff(String formattedFinishTime,
372      long finishTime, long startTime){
373    StringBuilder buf = new StringBuilder();
374    if (0 != finishTime) {
375      buf.append(formattedFinishTime);
376      if (0 != startTime){
377        buf.append(" (" + formatTimeDiff(finishTime , startTime) + ")");
378      }
379    }
380    return buf.toString();
381  }
382  
383  /**
384   * Returns an arraylist of strings.
385   * @param str the comma seperated string values
386   * @return the arraylist of the comma seperated string values
387   */
388  public static String[] getStrings(String str){
389    String delim = ",";
390    return getStrings(str, delim);
391  }
392
393  /**
394   * Returns an arraylist of strings.
395   * @param str the string values
396   * @param delim delimiter to separate the values
397   * @return the arraylist of the seperated string values
398   */
399  public static String[] getStrings(String str, String delim){
400    Collection<String> values = getStringCollection(str, delim);
401    if(values.size() == 0) {
402      return null;
403    }
404    return values.toArray(new String[values.size()]);
405  }
406
407  /**
408   * Returns a collection of strings.
409   * @param str comma seperated string values
410   * @return an <code>ArrayList</code> of string values
411   */
412  public static Collection<String> getStringCollection(String str){
413    String delim = ",";
414    return getStringCollection(str, delim);
415  }
416
417  /**
418   * Returns a collection of strings.
419   * 
420   * @param str
421   *          String to parse
422   * @param delim
423   *          delimiter to separate the values
424   * @return Collection of parsed elements.
425   */
426  public static Collection<String> getStringCollection(String str, String delim) {
427    List<String> values = new ArrayList<String>();
428    if (str == null)
429      return values;
430    StringTokenizer tokenizer = new StringTokenizer(str, delim);
431    while (tokenizer.hasMoreTokens()) {
432      values.add(tokenizer.nextToken());
433    }
434    return values;
435  }
436
437  /**
438   * Splits a comma separated value <code>String</code>, trimming leading and
439   * trailing whitespace on each value. Duplicate and empty values are removed.
440   *
441   * @param str a comma separated <String> with values, may be null
442   * @return a <code>Collection</code> of <code>String</code> values, empty
443   *         Collection if null String input
444   */
445  public static Collection<String> getTrimmedStringCollection(String str){
446    Set<String> set = new LinkedHashSet<String>(
447      Arrays.asList(getTrimmedStrings(str)));
448    set.remove("");
449    return set;
450  }
451  
452  /**
453   * Splits a comma separated value <code>String</code>, trimming leading and
454   * trailing whitespace on each value.
455   *
456   * @param str a comma separated <code>String</code> with values, may be null
457   * @return an array of <code>String</code> values, empty array if null String
458   *         input
459   */
460  public static String[] getTrimmedStrings(String str){
461    if (null == str || str.trim().isEmpty()) {
462      return emptyStringArray;
463    }
464
465    return str.trim().split("\\s*,\\s*");
466  }
467
468  final public static String[] emptyStringArray = {};
469  final public static char COMMA = ',';
470  final public static String COMMA_STR = ",";
471  final public static char ESCAPE_CHAR = '\\';
472  
473  /**
474   * Split a string using the default separator
475   * @param str a string that may have escaped separator
476   * @return an array of strings
477   */
478  public static String[] split(String str) {
479    return split(str, ESCAPE_CHAR, COMMA);
480  }
481  
482  /**
483   * Split a string using the given separator
484   * @param str a string that may have escaped separator
485   * @param escapeChar a char that be used to escape the separator
486   * @param separator a separator char
487   * @return an array of strings
488   */
489  public static String[] split(
490      String str, char escapeChar, char separator) {
491    if (str==null) {
492      return null;
493    }
494    ArrayList<String> strList = new ArrayList<String>();
495    StringBuilder split = new StringBuilder();
496    int index = 0;
497    while ((index = findNext(str, separator, escapeChar, index, split)) >= 0) {
498      ++index; // move over the separator for next search
499      strList.add(split.toString());
500      split.setLength(0); // reset the buffer 
501    }
502    strList.add(split.toString());
503    // remove trailing empty split(s)
504    int last = strList.size(); // last split
505    while (--last>=0 && "".equals(strList.get(last))) {
506      strList.remove(last);
507    }
508    return strList.toArray(new String[strList.size()]);
509  }
510
511  /**
512   * Split a string using the given separator, with no escaping performed.
513   * @param str a string to be split. Note that this may not be null.
514   * @param separator a separator char
515   * @return an array of strings
516   */
517  public static String[] split(
518      String str, char separator) {
519    // String.split returns a single empty result for splitting the empty
520    // string.
521    if (str.isEmpty()) {
522      return new String[]{""};
523    }
524    ArrayList<String> strList = new ArrayList<String>();
525    int startIndex = 0;
526    int nextIndex = 0;
527    while ((nextIndex = str.indexOf(separator, startIndex)) != -1) {
528      strList.add(str.substring(startIndex, nextIndex));
529      startIndex = nextIndex + 1;
530    }
531    strList.add(str.substring(startIndex));
532    // remove trailing empty split(s)
533    int last = strList.size(); // last split
534    while (--last>=0 && "".equals(strList.get(last))) {
535      strList.remove(last);
536    }
537    return strList.toArray(new String[strList.size()]);
538  }
539  
540  /**
541   * Finds the first occurrence of the separator character ignoring the escaped
542   * separators starting from the index. Note the substring between the index
543   * and the position of the separator is passed.
544   * @param str the source string
545   * @param separator the character to find
546   * @param escapeChar character used to escape
547   * @param start from where to search
548   * @param split used to pass back the extracted string
549   */
550  public static int findNext(String str, char separator, char escapeChar, 
551                             int start, StringBuilder split) {
552    int numPreEscapes = 0;
553    for (int i = start; i < str.length(); i++) {
554      char curChar = str.charAt(i);
555      if (numPreEscapes == 0 && curChar == separator) { // separator 
556        return i;
557      } else {
558        split.append(curChar);
559        numPreEscapes = (curChar == escapeChar)
560                        ? (++numPreEscapes) % 2
561                        : 0;
562      }
563    }
564    return -1;
565  }
566  
567  /**
568   * Escape commas in the string using the default escape char
569   * @param str a string
570   * @return an escaped string
571   */
572  public static String escapeString(String str) {
573    return escapeString(str, ESCAPE_CHAR, COMMA);
574  }
575  
576  /**
577   * Escape <code>charToEscape</code> in the string 
578   * with the escape char <code>escapeChar</code>
579   * 
580   * @param str string
581   * @param escapeChar escape char
582   * @param charToEscape the char to be escaped
583   * @return an escaped string
584   */
585  public static String escapeString(
586      String str, char escapeChar, char charToEscape) {
587    return escapeString(str, escapeChar, new char[] {charToEscape});
588  }
589  
590  // check if the character array has the character 
591  private static boolean hasChar(char[] chars, char character) {
592    for (char target : chars) {
593      if (character == target) {
594        return true;
595      }
596    }
597    return false;
598  }
599  
600  /**
601   * @param charsToEscape array of characters to be escaped
602   */
603  public static String escapeString(String str, char escapeChar, 
604                                    char[] charsToEscape) {
605    if (str == null) {
606      return null;
607    }
608    StringBuilder result = new StringBuilder();
609    for (int i=0; i<str.length(); i++) {
610      char curChar = str.charAt(i);
611      if (curChar == escapeChar || hasChar(charsToEscape, curChar)) {
612        // special char
613        result.append(escapeChar);
614      }
615      result.append(curChar);
616    }
617    return result.toString();
618  }
619  
620  /**
621   * Unescape commas in the string using the default escape char
622   * @param str a string
623   * @return an unescaped string
624   */
625  public static String unEscapeString(String str) {
626    return unEscapeString(str, ESCAPE_CHAR, COMMA);
627  }
628  
629  /**
630   * Unescape <code>charToEscape</code> in the string 
631   * with the escape char <code>escapeChar</code>
632   * 
633   * @param str string
634   * @param escapeChar escape char
635   * @param charToEscape the escaped char
636   * @return an unescaped string
637   */
638  public static String unEscapeString(
639      String str, char escapeChar, char charToEscape) {
640    return unEscapeString(str, escapeChar, new char[] {charToEscape});
641  }
642  
643  /**
644   * @param charsToEscape array of characters to unescape
645   */
646  public static String unEscapeString(String str, char escapeChar, 
647                                      char[] charsToEscape) {
648    if (str == null) {
649      return null;
650    }
651    StringBuilder result = new StringBuilder(str.length());
652    boolean hasPreEscape = false;
653    for (int i=0; i<str.length(); i++) {
654      char curChar = str.charAt(i);
655      if (hasPreEscape) {
656        if (curChar != escapeChar && !hasChar(charsToEscape, curChar)) {
657          // no special char
658          throw new IllegalArgumentException("Illegal escaped string " + str + 
659              " unescaped " + escapeChar + " at " + (i-1));
660        } 
661        // otherwise discard the escape char
662        result.append(curChar);
663        hasPreEscape = false;
664      } else {
665        if (hasChar(charsToEscape, curChar)) {
666          throw new IllegalArgumentException("Illegal escaped string " + str + 
667              " unescaped " + curChar + " at " + i);
668        } else if (curChar == escapeChar) {
669          hasPreEscape = true;
670        } else {
671          result.append(curChar);
672        }
673      }
674    }
675    if (hasPreEscape ) {
676      throw new IllegalArgumentException("Illegal escaped string " + str + 
677          ", not expecting " + escapeChar + " in the end." );
678    }
679    return result.toString();
680  }
681  
682  /**
683   * Return a message for logging.
684   * @param prefix prefix keyword for the message
685   * @param msg content of the message
686   * @return a message for logging
687   */
688  private static String toStartupShutdownString(String prefix, String [] msg) {
689    StringBuilder b = new StringBuilder(prefix);
690    b.append("\n/************************************************************");
691    for(String s : msg)
692      b.append("\n" + prefix + s);
693    b.append("\n************************************************************/");
694    return b.toString();
695  }
696
697  /**
698   * Print a log message for starting up and shutting down
699   * @param clazz the class of the server
700   * @param args arguments
701   * @param LOG the target log object
702   */
703  public static void startupShutdownMessage(Class<?> clazz, String[] args,
704                                     final org.apache.commons.logging.Log LOG) {
705    startupShutdownMessage(clazz, args, LogAdapter.create(LOG));
706  }
707
708  /**
709   * Print a log message for starting up and shutting down
710   * @param clazz the class of the server
711   * @param args arguments
712   * @param LOG the target log object
713   */
714  public static void startupShutdownMessage(Class<?> clazz, String[] args,
715                                     final org.slf4j.Logger LOG) {
716    startupShutdownMessage(clazz, args, LogAdapter.create(LOG));
717  }
718
719  static void startupShutdownMessage(Class<?> clazz, String[] args,
720                                     final LogAdapter LOG) { 
721    final String hostname = NetUtils.getHostname();
722    final String classname = clazz.getSimpleName();
723    LOG.info(
724        toStartupShutdownString("STARTUP_MSG: ", new String[] {
725            "Starting " + classname,
726            "  user = " + System.getProperty("user.name"),
727            "  host = " + hostname,
728            "  args = " + Arrays.asList(args),
729            "  version = " + VersionInfo.getVersion(),
730            "  classpath = " + System.getProperty("java.class.path"),
731            "  build = " + VersionInfo.getUrl() + " -r "
732                         + VersionInfo.getRevision()  
733                         + "; compiled by '" + VersionInfo.getUser()
734                         + "' on " + VersionInfo.getDate(),
735            "  java = " + System.getProperty("java.version") }
736        )
737      );
738
739    if (SystemUtils.IS_OS_UNIX) {
740      try {
741        SignalLogger.INSTANCE.register(LOG);
742      } catch (Throwable t) {
743        LOG.warn("failed to register any UNIX signal loggers: ", t);
744      }
745    }
746    ShutdownHookManager.get().addShutdownHook(
747      new Runnable() {
748        @Override
749        public void run() {
750          LOG.info(toStartupShutdownString("SHUTDOWN_MSG: ", new String[]{
751            "Shutting down " + classname + " at " + hostname}));
752        }
753      }, SHUTDOWN_HOOK_PRIORITY);
754
755  }
756
757  /**
758   * The traditional binary prefixes, kilo, mega, ..., exa,
759   * which can be represented by a 64-bit integer.
760   * TraditionalBinaryPrefix symbol are case insensitive. 
761   */
762  public static enum TraditionalBinaryPrefix {
763    KILO(10),
764    MEGA(KILO.bitShift + 10),
765    GIGA(MEGA.bitShift + 10),
766    TERA(GIGA.bitShift + 10),
767    PETA(TERA.bitShift + 10),
768    EXA (PETA.bitShift + 10);
769
770    public final long value;
771    public final char symbol;
772    public final int bitShift;
773    public final long bitMask;
774
775    private TraditionalBinaryPrefix(int bitShift) {
776      this.bitShift = bitShift;
777      this.value = 1L << bitShift;
778      this.bitMask = this.value - 1L;
779      this.symbol = toString().charAt(0);
780    }
781
782    /**
783     * @return The TraditionalBinaryPrefix object corresponding to the symbol.
784     */
785    public static TraditionalBinaryPrefix valueOf(char symbol) {
786      symbol = Character.toUpperCase(symbol);
787      for(TraditionalBinaryPrefix prefix : TraditionalBinaryPrefix.values()) {
788        if (symbol == prefix.symbol) {
789          return prefix;
790        }
791      }
792      throw new IllegalArgumentException("Unknown symbol '" + symbol + "'");
793    }
794
795    /**
796     * Convert a string to long.
797     * The input string is first be trimmed
798     * and then it is parsed with traditional binary prefix.
799     *
800     * For example,
801     * "-1230k" will be converted to -1230 * 1024 = -1259520;
802     * "891g" will be converted to 891 * 1024^3 = 956703965184;
803     *
804     * @param s input string
805     * @return a long value represented by the input string.
806     */
807    public static long string2long(String s) {
808      s = s.trim();
809      final int lastpos = s.length() - 1;
810      final char lastchar = s.charAt(lastpos);
811      if (Character.isDigit(lastchar))
812        return Long.parseLong(s);
813      else {
814        long prefix;
815        try {
816          prefix = TraditionalBinaryPrefix.valueOf(lastchar).value;
817        } catch (IllegalArgumentException e) {
818          throw new IllegalArgumentException("Invalid size prefix '" + lastchar
819              + "' in '" + s
820              + "'. Allowed prefixes are k, m, g, t, p, e(case insensitive)");
821        }
822        long num = Long.parseLong(s.substring(0, lastpos));
823        if (num > (Long.MAX_VALUE/prefix) || num < (Long.MIN_VALUE/prefix)) {
824          throw new IllegalArgumentException(s + " does not fit in a Long");
825        }
826        return num * prefix;
827      }
828    }
829
830    /**
831     * Convert a long integer to a string with traditional binary prefix.
832     * 
833     * @param n the value to be converted
834     * @param unit The unit, e.g. "B" for bytes.
835     * @param decimalPlaces The number of decimal places.
836     * @return a string with traditional binary prefix.
837     */
838    public static String long2String(long n, String unit, int decimalPlaces) {
839      if (unit == null) {
840        unit = "";
841      }
842      //take care a special case
843      if (n == Long.MIN_VALUE) {
844        return "-8 " + EXA.symbol + unit;
845      }
846
847      final StringBuilder b = new StringBuilder();
848      //take care negative numbers
849      if (n < 0) {
850        b.append('-');
851        n = -n;
852      }
853      if (n < KILO.value) {
854        //no prefix
855        b.append(n);
856        return (unit.isEmpty()? b: b.append(" ").append(unit)).toString();
857      } else {
858        //find traditional binary prefix
859        int i = 0;
860        for(; i < values().length && n >= values()[i].value; i++);
861        TraditionalBinaryPrefix prefix = values()[i - 1];
862
863        if ((n & prefix.bitMask) == 0) {
864          //exact division
865          b.append(n >> prefix.bitShift);
866        } else {
867          final String  format = "%." + decimalPlaces + "f";
868          String s = format(format, n/(double)prefix.value);
869          //check a special rounding up case
870          if (s.startsWith("1024")) {
871            prefix = values()[i];
872            s = format(format, n/(double)prefix.value);
873          }
874          b.append(s);
875        }
876        return b.append(' ').append(prefix.symbol).append(unit).toString();
877      }
878    }
879  }
880
881    /**
882     * Escapes HTML Special characters present in the string.
883     * @param string
884     * @return HTML Escaped String representation
885     */
886    public static String escapeHTML(String string) {
887      if(string == null) {
888        return null;
889      }
890      StringBuilder sb = new StringBuilder();
891      boolean lastCharacterWasSpace = false;
892      char[] chars = string.toCharArray();
893      for(char c : chars) {
894        if(c == ' ') {
895          if(lastCharacterWasSpace){
896            lastCharacterWasSpace = false;
897            sb.append("&nbsp;");
898          }else {
899            lastCharacterWasSpace=true;
900            sb.append(" ");
901          }
902        }else {
903          lastCharacterWasSpace = false;
904          switch(c) {
905          case '<': sb.append("&lt;"); break;
906          case '>': sb.append("&gt;"); break;
907          case '&': sb.append("&amp;"); break;
908          case '"': sb.append("&quot;"); break;
909          default : sb.append(c);break;
910          }
911        }
912      }
913      
914      return sb.toString();
915    }
916
917  /**
918   * @return a byte description of the given long interger value.
919   */
920  public static String byteDesc(long len) {
921    return TraditionalBinaryPrefix.long2String(len, "B", 2);
922  }
923
924  /** @deprecated use StringUtils.format("%.2f", d). */
925  @Deprecated
926  public static String limitDecimalTo2(double d) {
927    return format("%.2f", d);
928  }
929  
930  /**
931   * Concatenates strings, using a separator.
932   *
933   * @param separator Separator to join with.
934   * @param strings Strings to join.
935   */
936  public static String join(CharSequence separator, Iterable<?> strings) {
937    Iterator<?> i = strings.iterator();
938    if (!i.hasNext()) {
939      return "";
940    }
941    StringBuilder sb = new StringBuilder(i.next().toString());
942    while (i.hasNext()) {
943      sb.append(separator);
944      sb.append(i.next().toString());
945    }
946    return sb.toString();
947  }
948
949  public static String join(char separator, Iterable<?> strings) {
950    return join(separator + "", strings);
951  }
952
953  /**
954   * Concatenates strings, using a separator.
955   *
956   * @param separator to join with
957   * @param strings to join
958   * @return  the joined string
959   */
960  public static String join(CharSequence separator, String[] strings) {
961    // Ideally we don't have to duplicate the code here if array is iterable.
962    StringBuilder sb = new StringBuilder();
963    boolean first = true;
964    for (String s : strings) {
965      if (first) {
966        first = false;
967      } else {
968        sb.append(separator);
969      }
970      sb.append(s);
971    }
972    return sb.toString();
973  }
974
975  public static String join(char separator, String[] strings) {
976    return join(separator + "", strings);
977  }
978
979  /**
980   * Convert SOME_STUFF to SomeStuff
981   *
982   * @param s input string
983   * @return camelized string
984   */
985  public static String camelize(String s) {
986    StringBuilder sb = new StringBuilder();
987    String[] words = split(StringUtils.toLowerCase(s), ESCAPE_CHAR,  '_');
988
989    for (String word : words)
990      sb.append(org.apache.commons.lang.StringUtils.capitalize(word));
991
992    return sb.toString();
993  }
994
995  /**
996   * Matches a template string against a pattern, replaces matched tokens with
997   * the supplied replacements, and returns the result.  The regular expression
998   * must use a capturing group.  The value of the first capturing group is used
999   * to look up the replacement.  If no replacement is found for the token, then
1000   * it is replaced with the empty string.
1001   * 
1002   * For example, assume template is "%foo%_%bar%_%baz%", pattern is "%(.*?)%",
1003   * and replacements contains 2 entries, mapping "foo" to "zoo" and "baz" to
1004   * "zaz".  The result returned would be "zoo__zaz".
1005   * 
1006   * @param template String template to receive replacements
1007   * @param pattern Pattern to match for identifying tokens, must use a capturing
1008   *   group
1009   * @param replacements Map<String, String> mapping tokens identified by the
1010   *   capturing group to their replacement values
1011   * @return String template with replacements
1012   */
1013  public static String replaceTokens(String template, Pattern pattern,
1014      Map<String, String> replacements) {
1015    StringBuffer sb = new StringBuffer();
1016    Matcher matcher = pattern.matcher(template);
1017    while (matcher.find()) {
1018      String replacement = replacements.get(matcher.group(1));
1019      if (replacement == null) {
1020        replacement = "";
1021      }
1022      matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement));
1023    }
1024    matcher.appendTail(sb);
1025    return sb.toString();
1026  }
1027  
1028  /**
1029   * Get stack trace for a given thread.
1030   */
1031  public static String getStackTrace(Thread t) {
1032    final StackTraceElement[] stackTrace = t.getStackTrace();
1033    StringBuilder str = new StringBuilder();
1034    for (StackTraceElement e : stackTrace) {
1035      str.append(e.toString() + "\n");
1036    }
1037    return str.toString();
1038  }
1039
1040  /**
1041   * From a list of command-line arguments, remove both an option and the 
1042   * next argument.
1043   *
1044   * @param name  Name of the option to remove.  Example: -foo.
1045   * @param args  List of arguments.
1046   * @return      null if the option was not found; the value of the 
1047   *              option otherwise.
1048   * @throws IllegalArgumentException if the option's argument is not present
1049   */
1050  public static String popOptionWithArgument(String name, List<String> args)
1051      throws IllegalArgumentException {
1052    String val = null;
1053    for (Iterator<String> iter = args.iterator(); iter.hasNext(); ) {
1054      String cur = iter.next();
1055      if (cur.equals("--")) {
1056        // stop parsing arguments when you see --
1057        break;
1058      } else if (cur.equals(name)) {
1059        iter.remove();
1060        if (!iter.hasNext()) {
1061          throw new IllegalArgumentException("option " + name + " requires 1 " +
1062              "argument.");
1063        }
1064        val = iter.next();
1065        iter.remove();
1066        break;
1067      }
1068    }
1069    return val;
1070  }
1071  
1072  /**
1073   * From a list of command-line arguments, remove an option.
1074   *
1075   * @param name  Name of the option to remove.  Example: -foo.
1076   * @param args  List of arguments.
1077   * @return      true if the option was found and removed; false otherwise.
1078   */
1079  public static boolean popOption(String name, List<String> args) {
1080    for (Iterator<String> iter = args.iterator(); iter.hasNext(); ) {
1081      String cur = iter.next();
1082      if (cur.equals("--")) {
1083        // stop parsing arguments when you see --
1084        break;
1085      } else if (cur.equals(name)) {
1086        iter.remove();
1087        return true;
1088      }
1089    }
1090    return false;
1091  }
1092  
1093  /**
1094   * From a list of command-line arguments, return the first non-option
1095   * argument.  Non-option arguments are those which either come after 
1096   * a double dash (--) or do not start with a dash.
1097   *
1098   * @param args  List of arguments.
1099   * @return      The first non-option argument, or null if there were none.
1100   */
1101  public static String popFirstNonOption(List<String> args) {
1102    for (Iterator<String> iter = args.iterator(); iter.hasNext(); ) {
1103      String cur = iter.next();
1104      if (cur.equals("--")) {
1105        if (!iter.hasNext()) {
1106          return null;
1107        }
1108        cur = iter.next();
1109        iter.remove();
1110        return cur;
1111      } else if (!cur.startsWith("-")) {
1112        iter.remove();
1113        return cur;
1114      }
1115    }
1116    return null;
1117  }
1118
1119  /**
1120   * Converts all of the characters in this String to lower case with
1121   * Locale.ENGLISH.
1122   *
1123   * @param str  string to be converted
1124   * @return     the str, converted to lowercase.
1125   */
1126  public static String toLowerCase(String str) {
1127    return str.toLowerCase(Locale.ENGLISH);
1128  }
1129
1130  /**
1131   * Converts all of the characters in this String to upper case with
1132   * Locale.ENGLISH.
1133   *
1134   * @param str  string to be converted
1135   * @return     the str, converted to uppercase.
1136   */
1137  public static String toUpperCase(String str) {
1138    return str.toUpperCase(Locale.ENGLISH);
1139  }
1140
1141  /**
1142   * Compare strings locale-freely by using String#equalsIgnoreCase.
1143   *
1144   * @param s1  Non-null string to be converted
1145   * @param s2  string to be converted
1146   * @return     the str, converted to uppercase.
1147   */
1148  public static boolean equalsIgnoreCase(String s1, String s2) {
1149    Preconditions.checkNotNull(s1);
1150    // don't check non-null against s2 to make the semantics same as
1151    // s1.equals(s2)
1152    return s1.equalsIgnoreCase(s2);
1153  }
1154
1155}