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