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 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(" ");
788 }else {
789 lastCharacterWasSpace=true;
790 sb.append(" ");
791 }
792 }else {
793 lastCharacterWasSpace = false;
794 switch(c) {
795 case '<': sb.append("<"); break;
796 case '>': sb.append(">"); break;
797 case '&': sb.append("&"); break;
798 case '"': sb.append("""); 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 }