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