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.StringTokenizer;
034
035 import org.apache.hadoop.classification.InterfaceAudience;
036 import org.apache.hadoop.classification.InterfaceStability;
037 import org.apache.hadoop.fs.Path;
038 import org.apache.hadoop.net.NetUtils;
039
040 import com.google.common.net.InetAddresses;
041
042 /**
043 * General string utils
044 */
045 @InterfaceAudience.Private
046 @InterfaceStability.Unstable
047 public class StringUtils {
048
049 /**
050 * Priority of the StringUtils shutdown hook.
051 */
052 public static final int SHUTDOWN_HOOK_PRIORITY = 0;
053
054 /**
055 * Make a string representation of the exception.
056 * @param e The exception to stringify
057 * @return A string with exception name and call stack.
058 */
059 public static String stringifyException(Throwable e) {
060 StringWriter stm = new StringWriter();
061 PrintWriter wrt = new PrintWriter(stm);
062 e.printStackTrace(wrt);
063 wrt.close();
064 return stm.toString();
065 }
066
067 /**
068 * Given a full hostname, return the word upto the first dot.
069 * @param fullHostname the full hostname
070 * @return the hostname to the first dot
071 */
072 public static String simpleHostname(String fullHostname) {
073 if (InetAddresses.isInetAddress(fullHostname)) {
074 return fullHostname;
075 }
076 int offset = fullHostname.indexOf('.');
077 if (offset != -1) {
078 return fullHostname.substring(0, offset);
079 }
080 return fullHostname;
081 }
082
083 /**
084 * Given an integer, return a string that is in an approximate, but human
085 * readable format.
086 * @param number the number to format
087 * @return a human readable form of the integer
088 *
089 * @deprecated use {@link TraditionalBinaryPrefix#long2String(long, String, int)}.
090 */
091 @Deprecated
092 public static String humanReadableInt(long number) {
093 return TraditionalBinaryPrefix.long2String(number, "", 1);
094 }
095
096 /** The same as String.format(Locale.ENGLISH, format, objects). */
097 public static String format(final String format, final Object... objects) {
098 return String.format(Locale.ENGLISH, format, objects);
099 }
100
101 /**
102 * Format a percentage for presentation to the user.
103 * @param fraction the percentage as a fraction, e.g. 0.1 = 10%
104 * @param decimalPlaces the number of decimal places
105 * @return a string representation of the percentage
106 */
107 public static String formatPercent(double fraction, int decimalPlaces) {
108 return format("%." + decimalPlaces + "f%%", fraction*100);
109 }
110
111 /**
112 * Given an array of strings, return a comma-separated list of its elements.
113 * @param strs Array of strings
114 * @return Empty string if strs.length is 0, comma separated list of strings
115 * otherwise
116 */
117
118 public static String arrayToString(String[] strs) {
119 if (strs.length == 0) { return ""; }
120 StringBuilder sbuf = new StringBuilder();
121 sbuf.append(strs[0]);
122 for (int idx = 1; idx < strs.length; idx++) {
123 sbuf.append(",");
124 sbuf.append(strs[idx]);
125 }
126 return sbuf.toString();
127 }
128
129 /**
130 * Given an array of bytes it will convert the bytes to a hex string
131 * representation of the bytes
132 * @param bytes
133 * @param start start index, inclusively
134 * @param end end index, exclusively
135 * @return hex string representation of the byte array
136 */
137 public static String byteToHexString(byte[] bytes, int start, int end) {
138 if (bytes == null) {
139 throw new IllegalArgumentException("bytes == null");
140 }
141 StringBuilder s = new StringBuilder();
142 for(int i = start; i < end; i++) {
143 s.append(format("%02x", bytes[i]));
144 }
145 return s.toString();
146 }
147
148 /** Same as byteToHexString(bytes, 0, bytes.length). */
149 public static String byteToHexString(byte bytes[]) {
150 return byteToHexString(bytes, 0, bytes.length);
151 }
152
153 /**
154 * Given a hexstring this will return the byte array corresponding to the
155 * string
156 * @param hex the hex String array
157 * @return a byte array that is a hex string representation of the given
158 * string. The size of the byte array is therefore hex.length/2
159 */
160 public static byte[] hexStringToByte(String hex) {
161 byte[] bts = new byte[hex.length() / 2];
162 for (int i = 0; i < bts.length; i++) {
163 bts[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
164 }
165 return bts;
166 }
167 /**
168 *
169 * @param uris
170 */
171 public static String uriToString(URI[] uris){
172 if (uris == null) {
173 return null;
174 }
175 StringBuilder ret = new StringBuilder(uris[0].toString());
176 for(int i = 1; i < uris.length;i++){
177 ret.append(",");
178 ret.append(uris[i].toString());
179 }
180 return ret.toString();
181 }
182
183 /**
184 * @param str
185 * The string array to be parsed into an URI array.
186 * @return <tt>null</tt> if str is <tt>null</tt>, else the URI array
187 * equivalent to str.
188 * @throws IllegalArgumentException
189 * If any string in str violates RFC 2396.
190 */
191 public static URI[] stringToURI(String[] str){
192 if (str == null)
193 return null;
194 URI[] uris = new URI[str.length];
195 for (int i = 0; i < str.length;i++){
196 try{
197 uris[i] = new URI(str[i]);
198 }catch(URISyntaxException ur){
199 throw new IllegalArgumentException(
200 "Failed to create uri for " + str[i], ur);
201 }
202 }
203 return uris;
204 }
205
206 /**
207 *
208 * @param str
209 */
210 public static Path[] stringToPath(String[] str){
211 if (str == null) {
212 return null;
213 }
214 Path[] p = new Path[str.length];
215 for (int i = 0; i < str.length;i++){
216 p[i] = new Path(str[i]);
217 }
218 return p;
219 }
220 /**
221 *
222 * Given a finish and start time in long milliseconds, returns a
223 * String in the format Xhrs, Ymins, Z sec, for the time difference between two times.
224 * If finish time comes before start time then negative valeus of X, Y and Z wil return.
225 *
226 * @param finishTime finish time
227 * @param startTime start time
228 */
229 public static String formatTimeDiff(long finishTime, long startTime){
230 long timeDiff = finishTime - startTime;
231 return formatTime(timeDiff);
232 }
233
234 /**
235 *
236 * Given the time in long milliseconds, returns a
237 * String in the format Xhrs, Ymins, Z sec.
238 *
239 * @param timeDiff The time difference to format
240 */
241 public static String formatTime(long timeDiff){
242 StringBuilder buf = new StringBuilder();
243 long hours = timeDiff / (60*60*1000);
244 long rem = (timeDiff % (60*60*1000));
245 long minutes = rem / (60*1000);
246 rem = rem % (60*1000);
247 long seconds = rem / 1000;
248
249 if (hours != 0){
250 buf.append(hours);
251 buf.append("hrs, ");
252 }
253 if (minutes != 0){
254 buf.append(minutes);
255 buf.append("mins, ");
256 }
257 // return "0sec if no difference
258 buf.append(seconds);
259 buf.append("sec");
260 return buf.toString();
261 }
262 /**
263 * Formats time in ms and appends difference (finishTime - startTime)
264 * as returned by formatTimeDiff().
265 * If finish time is 0, empty string is returned, if start time is 0
266 * then difference is not appended to return value.
267 * @param dateFormat date format to use
268 * @param finishTime fnish time
269 * @param startTime start time
270 * @return formatted value.
271 */
272 public static String getFormattedTimeWithDiff(DateFormat dateFormat,
273 long finishTime, long startTime){
274 StringBuilder buf = new StringBuilder();
275 if (0 != finishTime) {
276 buf.append(dateFormat.format(new Date(finishTime)));
277 if (0 != startTime){
278 buf.append(" (" + formatTimeDiff(finishTime , startTime) + ")");
279 }
280 }
281 return buf.toString();
282 }
283
284 /**
285 * Returns an arraylist of strings.
286 * @param str the comma seperated string values
287 * @return the arraylist of the comma seperated string values
288 */
289 public static String[] getStrings(String str){
290 Collection<String> values = getStringCollection(str);
291 if(values.size() == 0) {
292 return null;
293 }
294 return values.toArray(new String[values.size()]);
295 }
296
297 /**
298 * Returns a collection of strings.
299 * @param str comma seperated string values
300 * @return an <code>ArrayList</code> of string values
301 */
302 public static Collection<String> getStringCollection(String str){
303 List<String> values = new ArrayList<String>();
304 if (str == null)
305 return values;
306 StringTokenizer tokenizer = new StringTokenizer (str,",");
307 values = new ArrayList<String>();
308 while (tokenizer.hasMoreTokens()) {
309 values.add(tokenizer.nextToken());
310 }
311 return values;
312 }
313
314 /**
315 * Splits a comma separated value <code>String</code>, trimming leading and trailing whitespace on each value.
316 * @param str a comma separated <String> with values
317 * @return a <code>Collection</code> of <code>String</code> values
318 */
319 public static Collection<String> getTrimmedStringCollection(String str){
320 return new ArrayList<String>(
321 Arrays.asList(getTrimmedStrings(str)));
322 }
323
324 /**
325 * Splits a comma separated value <code>String</code>, trimming leading and trailing whitespace on each value.
326 * @param str a comma separated <String> with values
327 * @return an array of <code>String</code> values
328 */
329 public static String[] getTrimmedStrings(String str){
330 if (null == str || "".equals(str.trim())) {
331 return emptyStringArray;
332 }
333
334 return str.trim().split("\\s*,\\s*");
335 }
336
337 final public static String[] emptyStringArray = {};
338 final public static char COMMA = ',';
339 final public static String COMMA_STR = ",";
340 final public static char ESCAPE_CHAR = '\\';
341
342 /**
343 * Split a string using the default separator
344 * @param str a string that may have escaped separator
345 * @return an array of strings
346 */
347 public static String[] split(String str) {
348 return split(str, ESCAPE_CHAR, COMMA);
349 }
350
351 /**
352 * Split a string using the given separator
353 * @param str a string that may have escaped separator
354 * @param escapeChar a char that be used to escape the separator
355 * @param separator a separator char
356 * @return an array of strings
357 */
358 public static String[] split(
359 String str, char escapeChar, char separator) {
360 if (str==null) {
361 return null;
362 }
363 ArrayList<String> strList = new ArrayList<String>();
364 StringBuilder split = new StringBuilder();
365 int index = 0;
366 while ((index = findNext(str, separator, escapeChar, index, split)) >= 0) {
367 ++index; // move over the separator for next search
368 strList.add(split.toString());
369 split.setLength(0); // reset the buffer
370 }
371 strList.add(split.toString());
372 // remove trailing empty split(s)
373 int last = strList.size(); // last split
374 while (--last>=0 && "".equals(strList.get(last))) {
375 strList.remove(last);
376 }
377 return strList.toArray(new String[strList.size()]);
378 }
379
380 /**
381 * Split a string using the given separator, with no escaping performed.
382 * @param str a string to be split. Note that this may not be null.
383 * @param separator a separator char
384 * @return an array of strings
385 */
386 public static String[] split(
387 String str, char separator) {
388 // String.split returns a single empty result for splitting the empty
389 // string.
390 if ("".equals(str)) {
391 return new String[]{""};
392 }
393 ArrayList<String> strList = new ArrayList<String>();
394 int startIndex = 0;
395 int nextIndex = 0;
396 while ((nextIndex = str.indexOf((int)separator, startIndex)) != -1) {
397 strList.add(str.substring(startIndex, nextIndex));
398 startIndex = nextIndex + 1;
399 }
400 strList.add(str.substring(startIndex));
401 // remove trailing empty split(s)
402 int last = strList.size(); // last split
403 while (--last>=0 && "".equals(strList.get(last))) {
404 strList.remove(last);
405 }
406 return strList.toArray(new String[strList.size()]);
407 }
408
409 /**
410 * Finds the first occurrence of the separator character ignoring the escaped
411 * separators starting from the index. Note the substring between the index
412 * and the position of the separator is passed.
413 * @param str the source string
414 * @param separator the character to find
415 * @param escapeChar character used to escape
416 * @param start from where to search
417 * @param split used to pass back the extracted string
418 */
419 public static int findNext(String str, char separator, char escapeChar,
420 int start, StringBuilder split) {
421 int numPreEscapes = 0;
422 for (int i = start; i < str.length(); i++) {
423 char curChar = str.charAt(i);
424 if (numPreEscapes == 0 && curChar == separator) { // separator
425 return i;
426 } else {
427 split.append(curChar);
428 numPreEscapes = (curChar == escapeChar)
429 ? (++numPreEscapes) % 2
430 : 0;
431 }
432 }
433 return -1;
434 }
435
436 /**
437 * Escape commas in the string using the default escape char
438 * @param str a string
439 * @return an escaped string
440 */
441 public static String escapeString(String str) {
442 return escapeString(str, ESCAPE_CHAR, COMMA);
443 }
444
445 /**
446 * Escape <code>charToEscape</code> in the string
447 * with the escape char <code>escapeChar</code>
448 *
449 * @param str string
450 * @param escapeChar escape char
451 * @param charToEscape the char to be escaped
452 * @return an escaped string
453 */
454 public static String escapeString(
455 String str, char escapeChar, char charToEscape) {
456 return escapeString(str, escapeChar, new char[] {charToEscape});
457 }
458
459 // check if the character array has the character
460 private static boolean hasChar(char[] chars, char character) {
461 for (char target : chars) {
462 if (character == target) {
463 return true;
464 }
465 }
466 return false;
467 }
468
469 /**
470 * @param charsToEscape array of characters to be escaped
471 */
472 public static String escapeString(String str, char escapeChar,
473 char[] charsToEscape) {
474 if (str == null) {
475 return null;
476 }
477 StringBuilder result = new StringBuilder();
478 for (int i=0; i<str.length(); i++) {
479 char curChar = str.charAt(i);
480 if (curChar == escapeChar || hasChar(charsToEscape, curChar)) {
481 // special char
482 result.append(escapeChar);
483 }
484 result.append(curChar);
485 }
486 return result.toString();
487 }
488
489 /**
490 * Unescape commas in the string using the default escape char
491 * @param str a string
492 * @return an unescaped string
493 */
494 public static String unEscapeString(String str) {
495 return unEscapeString(str, ESCAPE_CHAR, COMMA);
496 }
497
498 /**
499 * Unescape <code>charToEscape</code> in the string
500 * with the escape char <code>escapeChar</code>
501 *
502 * @param str string
503 * @param escapeChar escape char
504 * @param charToEscape the escaped char
505 * @return an unescaped string
506 */
507 public static String unEscapeString(
508 String str, char escapeChar, char charToEscape) {
509 return unEscapeString(str, escapeChar, new char[] {charToEscape});
510 }
511
512 /**
513 * @param charsToEscape array of characters to unescape
514 */
515 public static String unEscapeString(String str, char escapeChar,
516 char[] charsToEscape) {
517 if (str == null) {
518 return null;
519 }
520 StringBuilder result = new StringBuilder(str.length());
521 boolean hasPreEscape = false;
522 for (int i=0; i<str.length(); i++) {
523 char curChar = str.charAt(i);
524 if (hasPreEscape) {
525 if (curChar != escapeChar && !hasChar(charsToEscape, curChar)) {
526 // no special char
527 throw new IllegalArgumentException("Illegal escaped string " + str +
528 " unescaped " + escapeChar + " at " + (i-1));
529 }
530 // otherwise discard the escape char
531 result.append(curChar);
532 hasPreEscape = false;
533 } else {
534 if (hasChar(charsToEscape, curChar)) {
535 throw new IllegalArgumentException("Illegal escaped string " + str +
536 " unescaped " + curChar + " at " + i);
537 } else if (curChar == escapeChar) {
538 hasPreEscape = true;
539 } else {
540 result.append(curChar);
541 }
542 }
543 }
544 if (hasPreEscape ) {
545 throw new IllegalArgumentException("Illegal escaped string " + str +
546 ", not expecting " + escapeChar + " in the end." );
547 }
548 return result.toString();
549 }
550
551 /**
552 * Return a message for logging.
553 * @param prefix prefix keyword for the message
554 * @param msg content of the message
555 * @return a message for logging
556 */
557 private static String toStartupShutdownString(String prefix, String [] msg) {
558 StringBuilder b = new StringBuilder(prefix);
559 b.append("\n/************************************************************");
560 for(String s : msg)
561 b.append("\n" + prefix + s);
562 b.append("\n************************************************************/");
563 return b.toString();
564 }
565
566 /**
567 * Print a log message for starting up and shutting down
568 * @param clazz the class of the server
569 * @param args arguments
570 * @param LOG the target log object
571 */
572 public static void startupShutdownMessage(Class<?> clazz, String[] args,
573 final org.apache.commons.logging.Log LOG) {
574 final String hostname = NetUtils.getHostname();
575 final String classname = clazz.getSimpleName();
576 LOG.info(
577 toStartupShutdownString("STARTUP_MSG: ", new String[] {
578 "Starting " + classname,
579 " host = " + hostname,
580 " args = " + Arrays.asList(args),
581 " version = " + VersionInfo.getVersion(),
582 " classpath = " + System.getProperty("java.class.path"),
583 " build = " + VersionInfo.getUrl() + " -r "
584 + VersionInfo.getRevision()
585 + "; compiled by '" + VersionInfo.getUser()
586 + "' on " + VersionInfo.getDate(),
587 " java = " + System.getProperty("java.version") }
588 )
589 );
590
591 ShutdownHookManager.get().addShutdownHook(
592 new Runnable() {
593 @Override
594 public void run() {
595 LOG.info(toStartupShutdownString("SHUTDOWN_MSG: ", new String[]{
596 "Shutting down " + classname + " at " + hostname}));
597 }
598 }, SHUTDOWN_HOOK_PRIORITY);
599
600 }
601
602 /**
603 * The traditional binary prefixes, kilo, mega, ..., exa,
604 * which can be represented by a 64-bit integer.
605 * TraditionalBinaryPrefix symbol are case insensitive.
606 */
607 public static enum TraditionalBinaryPrefix {
608 KILO(10),
609 MEGA(KILO.bitShift + 10),
610 GIGA(MEGA.bitShift + 10),
611 TERA(GIGA.bitShift + 10),
612 PETA(TERA.bitShift + 10),
613 EXA (PETA.bitShift + 10);
614
615 public final long value;
616 public final char symbol;
617 public final int bitShift;
618 public final long bitMask;
619
620 private TraditionalBinaryPrefix(int bitShift) {
621 this.bitShift = bitShift;
622 this.value = 1L << bitShift;
623 this.bitMask = this.value - 1L;
624 this.symbol = toString().charAt(0);
625 }
626
627 /**
628 * @return The TraditionalBinaryPrefix object corresponding to the symbol.
629 */
630 public static TraditionalBinaryPrefix valueOf(char symbol) {
631 symbol = Character.toUpperCase(symbol);
632 for(TraditionalBinaryPrefix prefix : TraditionalBinaryPrefix.values()) {
633 if (symbol == prefix.symbol) {
634 return prefix;
635 }
636 }
637 throw new IllegalArgumentException("Unknown symbol '" + symbol + "'");
638 }
639
640 /**
641 * Convert a string to long.
642 * The input string is first be trimmed
643 * and then it is parsed with traditional binary prefix.
644 *
645 * For example,
646 * "-1230k" will be converted to -1230 * 1024 = -1259520;
647 * "891g" will be converted to 891 * 1024^3 = 956703965184;
648 *
649 * @param s input string
650 * @return a long value represented by the input string.
651 */
652 public static long string2long(String s) {
653 s = s.trim();
654 final int lastpos = s.length() - 1;
655 final char lastchar = s.charAt(lastpos);
656 if (Character.isDigit(lastchar))
657 return Long.parseLong(s);
658 else {
659 long prefix;
660 try {
661 prefix = TraditionalBinaryPrefix.valueOf(lastchar).value;
662 } catch (IllegalArgumentException e) {
663 throw new IllegalArgumentException("Invalid size prefix '" + lastchar
664 + "' in '" + s
665 + "'. Allowed prefixes are k, m, g, t, p, e(case insensitive)");
666 }
667 long num = Long.parseLong(s.substring(0, lastpos));
668 if (num > (Long.MAX_VALUE/prefix) || num < (Long.MIN_VALUE/prefix)) {
669 throw new IllegalArgumentException(s + " does not fit in a Long");
670 }
671 return num * prefix;
672 }
673 }
674
675 /**
676 * Convert a long integer to a string with traditional binary prefix.
677 *
678 * @param n the value to be converted
679 * @param unit The unit, e.g. "B" for bytes.
680 * @param decimalPlaces The number of decimal places.
681 * @return a string with traditional binary prefix.
682 */
683 public static String long2String(long n, String unit, int decimalPlaces) {
684 if (unit == null) {
685 unit = "";
686 }
687 //take care a special case
688 if (n == Long.MIN_VALUE) {
689 return "-8 " + EXA.symbol + unit;
690 }
691
692 final StringBuilder b = new StringBuilder();
693 //take care negative numbers
694 if (n < 0) {
695 b.append('-');
696 n = -n;
697 }
698 if (n < KILO.value) {
699 //no prefix
700 b.append(n);
701 return (unit.isEmpty()? b: b.append(" ").append(unit)).toString();
702 } else {
703 //find traditional binary prefix
704 int i = 0;
705 for(; i < values().length && n >= values()[i].value; i++);
706 TraditionalBinaryPrefix prefix = values()[i - 1];
707
708 if ((n & prefix.bitMask) == 0) {
709 //exact division
710 b.append(n >> prefix.bitShift);
711 } else {
712 final String format = "%." + decimalPlaces + "f";
713 String s = format(format, n/(double)prefix.value);
714 //check a special rounding up case
715 if (s.startsWith("1024")) {
716 prefix = values()[i];
717 s = format(format, n/(double)prefix.value);
718 }
719 b.append(s);
720 }
721 return b.append(' ').append(prefix.symbol).append(unit).toString();
722 }
723 }
724 }
725
726 /**
727 * Escapes HTML Special characters present in the string.
728 * @param string
729 * @return HTML Escaped String representation
730 */
731 public static String escapeHTML(String string) {
732 if(string == null) {
733 return null;
734 }
735 StringBuilder sb = new StringBuilder();
736 boolean lastCharacterWasSpace = false;
737 char[] chars = string.toCharArray();
738 for(char c : chars) {
739 if(c == ' ') {
740 if(lastCharacterWasSpace){
741 lastCharacterWasSpace = false;
742 sb.append(" ");
743 }else {
744 lastCharacterWasSpace=true;
745 sb.append(" ");
746 }
747 }else {
748 lastCharacterWasSpace = false;
749 switch(c) {
750 case '<': sb.append("<"); break;
751 case '>': sb.append(">"); break;
752 case '&': sb.append("&"); break;
753 case '"': sb.append("""); break;
754 default : sb.append(c);break;
755 }
756 }
757 }
758
759 return sb.toString();
760 }
761
762 /**
763 * @return a byte description of the given long interger value.
764 */
765 public static String byteDesc(long len) {
766 return TraditionalBinaryPrefix.long2String(len, "B", 2);
767 }
768
769 /** @deprecated use StringUtils.format("%.2f", d). */
770 @Deprecated
771 public static String limitDecimalTo2(double d) {
772 return format("%.2f", d);
773 }
774
775 /**
776 * Concatenates strings, using a separator.
777 *
778 * @param separator Separator to join with.
779 * @param strings Strings to join.
780 */
781 public static String join(CharSequence separator, Iterable<?> strings) {
782 Iterator<?> i = strings.iterator();
783 if (!i.hasNext()) {
784 return "";
785 }
786 StringBuilder sb = new StringBuilder(i.next().toString());
787 while (i.hasNext()) {
788 sb.append(separator);
789 sb.append(i.next().toString());
790 }
791 return sb.toString();
792 }
793
794 /**
795 * Convert SOME_STUFF to SomeStuff
796 *
797 * @param s input string
798 * @return camelized string
799 */
800 public static String camelize(String s) {
801 StringBuilder sb = new StringBuilder();
802 String[] words = split(s.toLowerCase(Locale.US), ESCAPE_CHAR, '_');
803
804 for (String word : words)
805 sb.append(org.apache.commons.lang.StringUtils.capitalize(word));
806
807 return sb.toString();
808 }
809 }