001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    
019    package org.apache.hadoop.util;
020    
021    import java.io.PrintWriter;
022    import java.io.StringWriter;
023    import java.net.URI;
024    import java.net.URISyntaxException;
025    import java.text.DateFormat;
026    import java.util.ArrayList;
027    import java.util.Arrays;
028    import java.util.Collection;
029    import java.util.Date;
030    import java.util.HashSet;
031    import java.util.Iterator;
032    import java.util.LinkedHashSet;
033    import java.util.List;
034    import java.util.Locale;
035    import java.util.Map;
036    import java.util.Set;
037    import java.util.StringTokenizer;
038    import java.util.regex.Matcher;
039    import java.util.regex.Pattern;
040    
041    import org.apache.commons.lang.SystemUtils;
042    import org.apache.hadoop.classification.InterfaceAudience;
043    import org.apache.hadoop.classification.InterfaceStability;
044    import org.apache.hadoop.fs.Path;
045    import org.apache.hadoop.net.NetUtils;
046    
047    import com.google.common.net.InetAddresses;
048    
049    /**
050     * General string utils
051     */
052    @InterfaceAudience.Private
053    @InterfaceStability.Unstable
054    public 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    }