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