001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.kernel.util;
016    
017    import com.liferay.portal.kernel.io.unsync.UnsyncBufferedReader;
018    import com.liferay.portal.kernel.io.unsync.UnsyncStringReader;
019    import com.liferay.portal.kernel.log.Log;
020    import com.liferay.portal.kernel.log.LogFactoryUtil;
021    import com.liferay.portal.kernel.search.util.SearchUtil;
022    import com.liferay.portal.kernel.security.RandomUtil;
023    
024    import java.io.IOException;
025    import java.io.InputStream;
026    import java.io.InputStreamReader;
027    
028    import java.net.URL;
029    
030    import java.util.ArrayList;
031    import java.util.Collection;
032    import java.util.Enumeration;
033    import java.util.List;
034    import java.util.Locale;
035    import java.util.Map;
036    import java.util.Random;
037    import java.util.regex.Pattern;
038    
039    /**
040     * The String utility class.
041     *
042     * @author Brian Wing Shun Chan
043     * @author Sandeep Soni
044     * @author Ganesh Ram
045     * @author Shuyang Zhou
046     * @author Hugo Huijser
047     */
048    public class StringUtil {
049    
050            /**
051             * Adds string <code>add</code> to string <code>s</code> resulting in a
052             * comma delimited list of strings, disallowing duplicate strings in the
053             * list.
054             *
055             * <p>
056             * The resulting string ends with a comma even if the original string does
057             * not.
058             * </p>
059             *
060             * @param  s the original string, representing a comma delimited list of
061             *         strings
062             * @param  add the string to add to the original, representing the string to
063             *         add to the list
064             * @return a string that represents the original string and the added string
065             *         separated by a comma, or <code>null</code> if the string to add
066             *         is <code>null</code>
067             */
068            public static String add(String s, String add) {
069                    return add(s, add, StringPool.COMMA);
070            }
071    
072            /**
073             * Adds string <code>add</code> to string <code>s</code> that represents a
074             * delimited list of strings, using a specified delimiter and disallowing
075             * duplicate words.
076             *
077             * <p>
078             * The returned string ends with the delimiter even if the original string
079             * does not.
080             * </p>
081             *
082             * @param  s the original string, representing a delimited list of strings
083             * @param  add the string to add to the original, representing the string to
084             *         add to the list
085             * @param  delimiter the delimiter used to separate strings in the list
086             * @return a string that represents the original string and the added string
087             *         separated by the delimiter, or <code>null</code> if the string to
088             *         add or the delimiter string is <code>null</code>
089             */
090            public static String add(String s, String add, String delimiter) {
091                    return add(s, add, delimiter, false);
092            }
093    
094            /**
095             * Adds string <code>add</code> to string <code>s</code> that represents a
096             * delimited list of strings, using a specified delimiter and optionally
097             * allowing duplicate words.
098             *
099             * <p>
100             * The returned string ends with the delimiter even if the original string
101             * does not.
102             * </p>
103             *
104             * @param  s the original string, representing a delimited list of strings
105             * @param  add the string to add to the original, representing the string to
106             *         add to the list
107             * @param  delimiter the delimiter used to separate strings in the list
108             * @param  allowDuplicates whether to allow duplicate strings
109             * @return a string that represents the original string and the added string
110             *         separated by the delimiter, or <code>null</code> if the string to
111             *         add or the delimiter string is <code>null</code>
112             */
113            public static String add(
114                    String s, String add, String delimiter, boolean allowDuplicates) {
115    
116                    if ((add == null) || (delimiter == null)) {
117                            return null;
118                    }
119    
120                    if (s == null) {
121                            s = StringPool.BLANK;
122                    }
123    
124                    if (allowDuplicates || !contains(s, add, delimiter)) {
125                            StringBundler sb = new StringBundler(4);
126    
127                            sb.append(s);
128    
129                            if (Validator.isNull(s) || s.endsWith(delimiter)) {
130                                    sb.append(add);
131                                    sb.append(delimiter);
132                            }
133                            else {
134                                    sb.append(delimiter);
135                                    sb.append(add);
136                                    sb.append(delimiter);
137                            }
138    
139                            s = sb.toString();
140                    }
141    
142                    return s;
143            }
144    
145            /**
146             * Returns the original string with an appended space followed by the string
147             * value of the suffix surrounded by parentheses.
148             *
149             * <p>
150             * If the original string ends with a numerical parenthetical suffix having
151             * an integer value equal to <code>suffix - 1</code>, then the existing
152             * parenthetical suffix is replaced by the new one.
153             * </p>
154             *
155             * <p>
156             * Examples:
157             * </p>
158             *
159             * <p>
160             * <pre>
161             * <code>
162             * appendParentheticalSuffix("file", 0) returns "file (0)"
163             * appendParentheticalSuffix("file (0)", 0) returns "file (0) (0)"
164             * appendParentheticalSuffix("file (0)", 1) returns "file (1)"
165             * appendParentheticalSuffix("file (0)", 2) returns "file (0) (2)"
166             * </code>
167             * </pre>
168             * </p>
169             *
170             * @param  s the original string
171             * @param  suffix the suffix to be appended
172             * @return the resultant string whose characters equal those of the original
173             *         string, followed by a space, followed by the specified suffix
174             *         enclosed in parentheses, or, if the difference between the
175             *         provided suffix and the existing suffix is 1, the existing suffix
176             *         is incremented by 1
177             */
178            public static String appendParentheticalSuffix(String s, int suffix) {
179                    if (Pattern.matches(".* \\(" + String.valueOf(suffix - 1) + "\\)", s)) {
180                            int pos = s.lastIndexOf(" (");
181    
182                            s = s.substring(0, pos);
183                    }
184    
185                    return appendParentheticalSuffix(s, String.valueOf(suffix));
186            }
187    
188            /**
189             * Returns the original string with an appended space followed by the suffix
190             * surrounded by parentheses.
191             *
192             * <p>
193             * Example:
194             * </p>
195             *
196             * <p>
197             * <pre>
198             * <code>
199             * appendParentheticalSuffix("Java", "EE") returns "Java (EE)"
200             * </code>
201             * </pre>
202             * </p>
203             *
204             * @param  s the original string
205             * @param  suffix the suffix to be appended
206             * @return a string that represents the original string, followed by a
207             *         space, followed by the suffix enclosed in parentheses
208             */
209            public static String appendParentheticalSuffix(String s, String suffix) {
210                    StringBundler sb = new StringBundler(5);
211    
212                    sb.append(s);
213                    sb.append(StringPool.SPACE);
214                    sb.append(StringPool.OPEN_PARENTHESIS);
215                    sb.append(suffix);
216                    sb.append(StringPool.CLOSE_PARENTHESIS);
217    
218                    return sb.toString();
219            }
220    
221            /**
222             * Converts an array of bytes to a string representing the bytes in
223             * hexadecimal form.
224             *
225             * @param  bytes the array of bytes to be converted
226             * @return the string representing the bytes in hexadecimal form
227             */
228            public static String bytesToHexString(byte[] bytes) {
229                    StringBundler sb = new StringBundler(bytes.length * 2);
230    
231                    for (byte b : bytes) {
232                            String hex = Integer.toHexString(0x0100 + (b & 0x00FF));
233    
234                            hex = hex.substring(1);
235    
236                            if (hex.length() < 2) {
237                                    sb.append("0");
238                            }
239    
240                            sb.append(hex);
241                    }
242    
243                    return sb.toString();
244            }
245    
246            /**
247             * Returns <code>true</code> if the string contains the text as a comma
248             * delimited list entry.
249             *
250             * <p>
251             * Example:
252             * </p>
253             *
254             * <p>
255             * <pre>
256             * <code>
257             * contains("one,two,three", "two") returns true
258             * contains("one,two,three", "thr") returns false
259             * </code>
260             * </pre>
261             * </p>
262             *
263             * @param  s the string in which to search
264             * @param  text the text to search for in the string
265             * @return <code>true</code> if the string contains the text as a comma
266             *         delimited list entry; <code>false</code> otherwise
267             */
268            public static boolean contains(String s, String text) {
269                    return contains(s, text, StringPool.COMMA);
270            }
271    
272            /**
273             * Returns <code>true</code> if the string contains the text as a delimited
274             * list entry.
275             *
276             * <p>
277             * Examples:
278             * </p>
279             *
280             * <p>
281             * <pre>
282             * <code>
283             * contains("three...two...one", "two", "...") returns true
284             * contains("three...two...one", "thr", "...") returns false
285             * </code>
286             * </pre>
287             * </p>
288             *
289             * @param  s the string in which to search
290             * @param  text the text to search for in the string
291             * @param  delimiter the delimiter
292             * @return <code>true</code> if the string contains the text as a delimited
293             *         list entry; <code>false</code> otherwise
294             */
295            public static boolean contains(String s, String text, String delimiter) {
296                    if ((s == null) || (text == null) || (delimiter == null)) {
297                            return false;
298                    }
299    
300                    if (!s.endsWith(delimiter)) {
301                            s = s.concat(delimiter);
302                    }
303    
304                    String dtd = delimiter.concat(text).concat(delimiter);
305    
306                    int pos = s.indexOf(dtd);
307    
308                    if (pos == -1) {
309                            String td = text.concat(delimiter);
310    
311                            if (s.startsWith(td)) {
312                                    return true;
313                            }
314    
315                            return false;
316                    }
317    
318                    return true;
319            }
320    
321            /**
322             * Returns the number of times the text appears in the string.
323             *
324             * @param  s the string in which to search
325             * @param  text the text to search for in the string
326             * @return the number of times the text appears in the string
327             */
328            public static int count(String s, String text) {
329                    if ((s == null) || (s.length() == 0) || (text == null) ||
330                            (text.length() == 0)) {
331    
332                            return 0;
333                    }
334    
335                    int count = 0;
336    
337                    int pos = s.indexOf(text);
338    
339                    while (pos != -1) {
340                            pos = s.indexOf(text, pos + text.length());
341    
342                            count++;
343                    }
344    
345                    return count;
346            }
347    
348            /**
349             * Returns <code>true</code> if the string ends with the specified
350             * character, ignoring case.
351             *
352             * @param  s the string in which to search
353             * @param  end the character to search for at the end of the string
354             * @return <code>true</code> if the string ends with the specified
355             *         character, ignoring case; <code>false</code> otherwise
356             */
357            public static boolean endsWith(String s, char end) {
358                    return endsWith(s, (new Character(end)).toString());
359            }
360    
361            /**
362             * Returns <code>true</code> if the string ends with the string
363             * <code>end</code>, ignoring case.
364             *
365             * @param  s the string in which to search
366             * @param  end the string to check for at the end of the string
367             * @return <code>true</code> if the string ends with the string
368             *         <code>end</code>, ignoring case; <code>false</code> otherwise
369             */
370            public static boolean endsWith(String s, String end) {
371                    if ((s == null) || (end == null)) {
372                            return false;
373                    }
374    
375                    if (end.length() > s.length()) {
376                            return false;
377                    }
378    
379                    String temp = s.substring(s.length() - end.length());
380    
381                    if (equalsIgnoreCase(temp, end)) {
382                            return true;
383                    }
384                    else {
385                            return false;
386                    }
387            }
388    
389            /**
390             * Returns <code>true</code> if the strings are equal, ignoring new line
391             * characters.
392             *
393             * @param  s1 the first string to compare
394             * @param  s2 the second string to compare
395             * @return <code>true</code> if the strings are equal, ignoring new line
396             *         characters; <code>false</code> otherwise
397             */
398            public static boolean equalsIgnoreBreakLine(String s1, String s2) {
399                    if (s1 == s2) {
400                            return true;
401                    }
402    
403                    if ((s1 == null) || (s2 == null)) {
404                            return false;
405                    }
406    
407                    s1 = replace(
408                            s1, new String[] {StringPool.RETURN_NEW_LINE, StringPool.NEW_LINE},
409                            new String[] {StringPool.BLANK, StringPool.BLANK});
410                    s2 = replace(
411                            s2, new String[] {StringPool.RETURN_NEW_LINE, StringPool.NEW_LINE},
412                            new String[] {StringPool.BLANK, StringPool.BLANK});
413    
414                    if (s1.length() != s2.length()) {
415                            return false;
416                    }
417    
418                    return s1.equals(s2);
419            }
420    
421            /**
422             * Returns <code>true</code> if the strings are equal, ignoring case.
423             *
424             * @param  s1 the first string to compare
425             * @param  s2 the second string to compare
426             * @return <code>true</code> if the strings are equal, ignoring case;
427             *         <code>false</code> otherwise
428             */
429            public static boolean equalsIgnoreCase(String s1, String s2) {
430                    if (s1 == s2) {
431                            return true;
432                    }
433    
434                    if ((s1 == null) || (s2 == null)) {
435                            return false;
436                    }
437    
438                    if (s1.length() != s2.length()) {
439                            return false;
440                    }
441    
442                    for (int i = 0; i < s1.length(); i++) {
443                            char c1 = s1.charAt(i);
444    
445                            char c2 = s2.charAt(i);
446    
447                            if (c1 == c2) {
448                                    continue;
449                            }
450    
451                            if ((c1 > 127) || (c2 > 127)) {
452    
453                                    // Georgian alphabet needs to check both upper and lower case
454    
455                                    if ((Character.toLowerCase(c1) == Character.toLowerCase(c2)) ||
456                                            (Character.toUpperCase(c1) == Character.toUpperCase(c2))) {
457    
458                                            continue;
459                                    }
460    
461                                    return false;
462                            }
463    
464                            int delta = c1 - c2;
465    
466                            if ((delta != 32) && (delta != -32)) {
467                                    return false;
468                            }
469                    }
470    
471                    return true;
472            }
473    
474            /**
475             * Returns the substring of each character instance in string <code>s</code>
476             * that is found in the character array <code>chars</code>. The substring of
477             * characters returned maintain their original order.
478             *
479             * @param  s the string from which to extract characters
480             * @param  chars the characters to extract from the string
481             * @return the substring of each character instance in string <code>s</code>
482             *         that is found in the character array <code>chars</code>, or an
483             *         empty string if the given string is <code>null</code>
484             */
485            public static String extract(String s, char[] chars) {
486                    if (s == null) {
487                            return StringPool.BLANK;
488                    }
489    
490                    StringBundler sb = new StringBundler();
491    
492                    for (char c1 : s.toCharArray()) {
493                            for (char c2 : chars) {
494                                    if (c1 == c2) {
495                                            sb.append(c1);
496    
497                                            break;
498                                    }
499                            }
500                    }
501    
502                    return sb.toString();
503            }
504    
505            /**
506             * Returns the substring of English characters from the string.
507             *
508             * @param  s the string from which to extract characters
509             * @return the substring of English characters from the string, or an empty
510             *         string if the given string is <code>null</code>
511             */
512            public static String extractChars(String s) {
513                    if (s == null) {
514                            return StringPool.BLANK;
515                    }
516    
517                    StringBundler sb = new StringBundler();
518    
519                    char[] chars = s.toCharArray();
520    
521                    for (char c : chars) {
522                            if (Validator.isChar(c)) {
523                                    sb.append(c);
524                            }
525                    }
526    
527                    return sb.toString();
528            }
529    
530            /**
531             * Returns a string consisting of all of the digits extracted from the
532             * string.
533             *
534             * @param  s the string from which to extract digits
535             * @return a string consisting of all of the digits extracted from the
536             *         string
537             */
538            public static String extractDigits(String s) {
539                    if (s == null) {
540                            return StringPool.BLANK;
541                    }
542    
543                    StringBundler sb = new StringBundler();
544    
545                    char[] chars = s.toCharArray();
546    
547                    for (char c : chars) {
548                            if (Validator.isDigit(c)) {
549                                    sb.append(c);
550                            }
551                    }
552    
553                    return sb.toString();
554            }
555    
556            /**
557             * Returns the substring of <code>s</code> up to but not including the first
558             * occurrence of the delimiter.
559             *
560             * @param  s the string from which to extract a substring
561             * @param  delimiter the character whose index in the string marks where to
562             *         end the substring
563             * @return the substring of <code>s</code> up to but not including the first
564             *         occurrence of the delimiter, <code>null</code> if the string is
565             *         <code>null</code> or the delimiter does not occur in the string
566             */
567            public static String extractFirst(String s, char delimiter) {
568                    if (s == null) {
569                            return null;
570                    }
571    
572                    int index = s.indexOf(delimiter);
573    
574                    if (index < 0) {
575                            return null;
576                    }
577                    else {
578                            return s.substring(0, index);
579                    }
580            }
581    
582            /**
583             * Returns the substring of <code>s</code> up to but not including the first
584             * occurrence of the delimiter.
585             *
586             * @param  s the string from which to extract a substring
587             * @param  delimiter the smaller string whose index in the larger string
588             *         marks where to end the substring
589             * @return the substring of <code>s</code> up to but not including the first
590             *         occurrence of the delimiter, <code>null</code> if the string is
591             *         <code>null</code> or the delimiter does not occur in the string
592             */
593            public static String extractFirst(String s, String delimiter) {
594                    if (s == null) {
595                            return null;
596                    }
597    
598                    int index = s.indexOf(delimiter);
599    
600                    if (index < 0) {
601                            return null;
602                    }
603                    else {
604                            return s.substring(0, index);
605                    }
606            }
607    
608            /**
609             * Returns the substring of <code>s</code> after but not including the last
610             * occurrence of the delimiter.
611             *
612             * @param  s the string from which to extract the substring
613             * @param  delimiter the character whose last index in the string marks
614             *         where to begin the substring
615             * @return the substring of <code>s</code> after but not including the last
616             *         occurrence of the delimiter, <code>null</code> if the string is
617             *         <code>null</code> or the delimiter does not occur in the string
618             */
619            public static String extractLast(String s, char delimiter) {
620                    if (s == null) {
621                            return null;
622                    }
623    
624                    int index = s.lastIndexOf(delimiter);
625    
626                    if (index < 0) {
627                            return null;
628                    }
629                    else {
630                            return s.substring(index + 1);
631                    }
632            }
633    
634            /**
635             * Returns the substring of <code>s</code> after but not including the last
636             * occurrence of the delimiter.
637             *
638             * @param  s the string from which to extract the substring
639             * @param  delimiter the string whose last index in the string marks where
640             *         to begin the substring
641             * @return the substring of <code>s</code> after but not including the last
642             *         occurrence of the delimiter, <code>null</code> if the string is
643             *         <code>null</code> or the delimiter does not occur in the string
644             */
645            public static String extractLast(String s, String delimiter) {
646                    if (s == null) {
647                            return null;
648                    }
649    
650                    int index = s.lastIndexOf(delimiter);
651    
652                    if (index < 0) {
653                            return null;
654                    }
655                    else {
656                            return s.substring(index + delimiter.length());
657                    }
658            }
659    
660            /**
661             * Returns the substring of all leading digits of string <code>s</code>, or
662             * an empty string if it has no leading digits.
663             *
664             * @param  s the string from which to extract the substring
665             * @return the substring of all leading digits of string <code>s</code>, or
666             *         an empty string if it has no leading digits
667             */
668            public static String extractLeadingDigits(String s) {
669                    if (s == null) {
670                            return StringPool.BLANK;
671                    }
672    
673                    StringBundler sb = new StringBundler();
674    
675                    char[] chars = s.toCharArray();
676    
677                    for (char c : chars) {
678                            if (Validator.isDigit(c)) {
679                                    sb.append(c);
680                            }
681                            else {
682                                    return sb.toString();
683                            }
684                    }
685    
686                    return sb.toString();
687            }
688    
689            /**
690             * @deprecated As of 7.0.0, moved to {@link SearchUtil#highlight(String,
691             *             String[])}}
692             */
693            @Deprecated
694            public static String highlight(String s, String[] queryTerms) {
695                    return SearchUtil.highlight(s, queryTerms);
696            }
697    
698            /**
699             * @deprecated As of 7.0.0, moved to {@link SearchUtil#highlight(String,
700             *             String[], String, String)}}
701             */
702            @Deprecated
703            public static String highlight(
704                    String s, String[] queryTerms, String highlight1, String highlight2) {
705    
706                    return SearchUtil.highlight(s, queryTerms, highlight1, highlight2);
707            }
708    
709            /**
710             * Returns the index within the string of the first occurrence of any
711             * character from the array.
712             *
713             * <p>
714             * A <code>null</code> string returns <code>-1</code>. A <code>null</code>
715             * or empty array returns <code>-1</code>.
716             * </p>
717             *
718             * <p>
719             * Examples:
720             * </p>
721             *
722             * <p>
723             * <pre>
724             * <code>
725             * indexOfAny(null, *) returns -1
726             * indexOfAny(*, null) returns -1
727             * indexOfAny(*, []) returns -1
728             * indexOfAny("zzabyycdxx", ['a','c']) returns 2
729             * indexOfAny("zzabyycdxx", ['c','a']) returns 2
730             * indexOfAny("zzabyycdxx", ['m','n']) returns -1
731             * </code>
732             * </pre>
733             * </p>
734             *
735             * @param  s the string to search (optionally <code>null</code>)
736             * @param  chars the characters to search for (optionally <code>null</code>)
737             * @return the index within the string of the first occurrence of any
738             *         character from the array, or <code>-1</code> if none of the
739             *         characters occur
740             */
741            public static int indexOfAny(String s, char[] chars) {
742                    if (s == null) {
743                            return -1;
744                    }
745    
746                    return indexOfAny(s, chars, 0, s.length() - 1);
747            }
748    
749            /**
750             * Returns the index within the string of the first occurrence of any
751             * character from the array, starting the search at the specified index
752             * within the string.
753             *
754             * <p>
755             * A <code>null</code> string returns <code>-1</code>. A <code>null</code>
756             * or empty array returns <code>-1</code>.
757             * </p>
758             *
759             * <p>
760             * Examples:
761             * </p>
762             *
763             * <p>
764             * <pre>
765             * <code>
766             * indexOfAny(null, *, *) returns -1
767             * indexOfAny(*, null, *) returns -1
768             * indexOfAny(*, [], *) returns -1
769             * indexOfAny("zzabyycdxx", ['a','c'], 3) returns 6
770             * </code>
771             * </pre>
772             * </p>
773             *
774             * @param  s the string to search (optionally <code>null</code>)
775             * @param  chars the characters to search for (optionally <code>null</code>)
776             * @param  fromIndex the start index within the string
777             * @return the index within the string of the first occurrence of any
778             *         character from the array, starting the search at the specified
779             *         index within the string, or <code>-1</code> if none of the
780             *         characters occur
781             */
782            public static int indexOfAny(String s, char[] chars, int fromIndex) {
783                    if (s == null) {
784                            return -1;
785                    }
786    
787                    return indexOfAny(s, chars, fromIndex, s.length() - 1);
788            }
789    
790            /**
791             * Returns the index within the string of the first occurrence of any
792             * character from the array, up to and including the specified end index
793             * within the string, starting the search at the specified start index
794             * within the string.
795             *
796             * <p>
797             * A <code>null</code> string returns <code>-1</code>. A <code>null</code>
798             * or empty array returns <code>-1</code>.
799             * </p>
800             *
801             * <p>
802             * Examples:
803             * </p>
804             *
805             * <p>
806             * <pre>
807             * <code>
808             * indexOfAny(null, *, *, *) returns -1
809             * indexOfAny(*, null, *, *) returns -1
810             * indexOfAny(*, [], *, *) returns -1
811             * indexOfAny("zzabyycdxx", ['a','c'], 3, 7) returns 6
812             * </code>
813             * </pre>
814             * </p>
815             *
816             * @param  s the string to search (optionally <code>null</code>)
817             * @param  chars the characters to search for (optionally <code>null</code>)
818             * @param  fromIndex the start index within the string
819             * @param  toIndex the end index within the string
820             * @return the index within the string of the first occurrence of any
821             *         character from the array, up to and including the specified end
822             *         index within the string, starting the search at the specified
823             *         start index within the string, or <code>-1</code> if none of the
824             *         characters occur
825             */
826            public static int indexOfAny(
827                    String s, char[] chars, int fromIndex, int toIndex) {
828    
829                    if ((s == null) || (toIndex < fromIndex)) {
830                            return -1;
831                    }
832    
833                    if (ArrayUtil.isEmpty(chars)) {
834                            return -1;
835                    }
836    
837                    if (fromIndex >= s.length()) {
838                            return -1;
839                    }
840    
841                    if (fromIndex < 0) {
842                            fromIndex = 0;
843                    }
844    
845                    if (toIndex >= s.length()) {
846                            toIndex = s.length() - 1;
847                    }
848    
849                    for (int i = fromIndex; i <= toIndex; i++) {
850                            char c = s.charAt(i);
851    
852                            for (int j = 0; j < chars.length; j++) {
853                                    if (c == chars[j]) {
854                                            return i;
855                                    }
856                            }
857                    }
858    
859                    return -1;
860            }
861    
862            /**
863             * Returns the index within the string of the first occurrence of any string
864             * from the array.
865             *
866             * <p>
867             * A <code>null</code> string returns <code>-1</code>. A <code>null</code>
868             * or empty array returns <code>-1</code>, but an array containing
869             * <code>""</code> returns <code>0</code> if the string is not
870             * <code>null</code>.
871             * </p>
872             *
873             * <p>
874             * Examples:
875             * </p>
876             *
877             * <p>
878             * <pre>
879             * <code>
880             * indexOfAny(null, *) returns -1
881             * indexOfAny(*, null) returns -1
882             * indexOfAny(*, [null]) returns -1
883             * indexOfAny(*, []) returns -1
884             * indexOfAny("zzabyycdxx", ["ab","cd"]) returns 2
885             * indexOfAny("zzabyycdxx", ["cd","ab"]) returns 2
886             * indexOfAny("zzabyycdxx", ["mn","op"]) returns -1
887             * indexOfAny("zzabyycdxx", ["mn",""]) returns 0
888             * </code>
889             * </pre>
890             * </p>
891             *
892             * @param  s the string (optionally <code>null</code>)
893             * @param  texts the strings to search for (optionally <code>null</code>)
894             * @return the index within the string of the first occurrence of any string
895             *         from the array, <code>0</code> if the search array contains
896             *         <code>""</code>, or <code>-1</code> if none of the strings occur
897             */
898            public static int indexOfAny(String s, String[] texts) {
899                    if (s == null) {
900                            return -1;
901                    }
902    
903                    return indexOfAny(s, texts, 0, s.length() - 1);
904            }
905    
906            /**
907             * Returns the index within the string of the first occurrence of any string
908             * from the array, starting the search at the specified index within the
909             * string.
910             *
911             * <p>
912             * A <code>null</code> string returns <code>-1</code>. A <code>null</code>
913             * or empty array returns <code>-1</code>, but an array containing
914             * <code>""</code> returns the specified start index if the string is not
915             * <code>null</code>.
916             * </p>
917             *
918             * <p>
919             * Examples:
920             * </p>
921             *
922             * <p>
923             * <pre>
924             * <code>
925             * indexOfAny(null, *, *) returns -1
926             * indexOfAny(*, null, *) returns -1
927             * indexOfAny(*, [null], *) returns -1
928             * indexOfAny(*, [], *) returns -1
929             * indexOfAny("zzabyycdxx", ["ab","cd"], 3) returns 6
930             * indexOfAny("zzabyycdxx", ["cd","ab"], 3) returns 6
931             * indexOfAny("zzabyycdxx", ["mn","op"], *) returns -1
932             * indexOfAny("zzabyycdxx", ["mn",""], 3) returns 3
933             * </code>
934             * </pre>
935             * </p>
936             *
937             * @param  s the string to search (optionally <code>null</code>)
938             * @param  texts the strings to search for (optionally <code>null</code>)
939             * @param  fromIndex the start index within the string
940             * @return the index within the string of the first occurrence of any string
941             *         from the array, starting the search at the specified index within
942             *         the string, the start index if the search array contains
943             *         <code>""</code>, or <code>-1</code> if none of the strings occur
944             */
945            public static int indexOfAny(String s, String[] texts, int fromIndex) {
946                    if (s == null) {
947                            return -1;
948                    }
949    
950                    return indexOfAny(s, texts, fromIndex, s.length() - 1);
951            }
952    
953            /**
954             * Returns the index within the string of the first occurrence of any string
955             * from the array, up to and including the specified end index within the
956             * string, starting the search at the specified start index within the
957             * string.
958             *
959             * <p>
960             * A <code>null</code> string returns <code>-1</code>. A <code>null</code>
961             * or empty array returns <code>-1</code>, but an array containing
962             * <code>""</code> returns the specified start index if the string is not
963             * <code>null</code>.
964             * </p>
965             *
966             * <p>
967             * Examples:
968             * </p>
969             *
970             * <p>
971             * <pre>
972             * <code>
973             * indexOfAny(null, *, *, *) returns -1
974             * indexOfAny(*, null, *, *) returns -1
975             * indexOfAny(*, [null], *, *) returns -1
976             * indexOfAny(*, [], *, *) returns -1
977             * indexOfAny("zzabyycdxx", ["ab","cd"], 3, 7) returns 6
978             * indexOfAny("zzabyycdxx", ["cd","ab"], 2, 7) returns 2
979             * indexOfAny("zzabyycdxx", ["mn","op"], *, *) returns -1
980             * indexOfAny("zzabyycdxx", ["mn",""], 3, *) returns 3
981             * </code>
982             * </pre>
983             * </p>
984             *
985             * @param  s the string to search (optionally <code>null</code>)
986             * @param  texts the strings to search for (optionally <code>null</code>)
987             * @param  fromIndex the start index within the string
988             * @param  toIndex the end index within the string
989             * @return the index within the string of the first occurrence of any string
990             *         from the array, up to and including the specified end index
991             *         within the string, starting the search at the specified start
992             *         index within the string, the start index if the search array
993             *         contains <code>""</code>, or <code>-1</code> if none of the
994             *         strings occur
995             */
996            public static int indexOfAny(
997                    String s, String[] texts, int fromIndex, int toIndex) {
998    
999                    if ((s == null) || (toIndex < fromIndex)) {
1000                            return -1;
1001                    }
1002    
1003                    if (ArrayUtil.isEmpty(texts)) {
1004                            return -1;
1005                    }
1006    
1007                    if (fromIndex >= s.length()) {
1008                            return -1;
1009                    }
1010    
1011                    if (fromIndex < 0) {
1012                            fromIndex = 0;
1013                    }
1014    
1015                    if (toIndex >= s.length()) {
1016                            toIndex = s.length() - 1;
1017                    }
1018    
1019                    for (int i = fromIndex; i <= toIndex; i++) {
1020                            for (int j = 0; j < texts.length; j++) {
1021                                    if (texts[j] == null) {
1022                                            continue;
1023                                    }
1024    
1025                                    if ((i + texts[j].length() <= toIndex + 1) &&
1026                                            s.startsWith(texts[j], i)) {
1027    
1028                                            return i;
1029                                    }
1030                            }
1031                    }
1032    
1033                    return -1;
1034            }
1035    
1036            /**
1037             * Inserts one string into the other at the specified offset index.
1038             *
1039             * @param  s the original string
1040             * @param  insert the string to be inserted into the original string
1041             * @param  offset the index of the original string where the insertion
1042             *         should take place
1043             * @return a string representing the original string with the other string
1044             *         inserted at the specified offset index, or <code>null</code> if
1045             *         the original string is <code>null</code>
1046             */
1047            public static String insert(String s, String insert, int offset) {
1048                    if (s == null) {
1049                            return null;
1050                    }
1051    
1052                    if (insert == null) {
1053                            return s;
1054                    }
1055    
1056                    if (offset > s.length()) {
1057                            return s.concat(insert);
1058                    }
1059    
1060                    String prefix = s.substring(0, offset);
1061                    String postfix = s.substring(offset);
1062    
1063                    return prefix.concat(insert).concat(postfix);
1064            }
1065    
1066            /**
1067             * Returns <code>true</code> if all the characters in string <code>s</code>
1068             * are lower case, ignoring any non-alphabetic characters.
1069             *
1070             * @param  s the string in which to search
1071             * @return <code>true</code> if all the characters in string <code>s</code>
1072             *         are lower case, ignoring any non-alphabetic characters;
1073             *         <code>false</code> otherwise
1074             */
1075            public static boolean isLowerCase(String s) {
1076                    if (s == null) {
1077                            return false;
1078                    }
1079    
1080                    for (int i = 0; i < s.length(); i++) {
1081                            char c = s.charAt(i);
1082    
1083                            // Fast path for ascii code, fallback to the slow unicode detection
1084    
1085                            if (c <= 127) {
1086                                    if ((c >= CharPool.UPPER_CASE_A) &&
1087                                            (c <= CharPool.UPPER_CASE_Z)) {
1088    
1089                                            return false;
1090                                    }
1091    
1092                                    continue;
1093                            }
1094    
1095                            if (Character.isLetter(c) && Character.isUpperCase(c)) {
1096                                    return false;
1097                            }
1098                    }
1099    
1100                    return true;
1101            }
1102    
1103            /**
1104             * Returns <code>true</code> if all the characters in string <code>s</code>
1105             * are upper case, ignoring any non-alphabetic characters.
1106             *
1107             * @param  s the string in which to search
1108             * @return <code>true</code> if all the characters in string <code>s</code>
1109             *         are upper case, ignoring any non-alphabetic characters;
1110             *         <code>false</code> otherwise
1111             */
1112            public static boolean isUpperCase(String s) {
1113                    if (s == null) {
1114                            return false;
1115                    }
1116    
1117                    for (int i = 0; i < s.length(); i++) {
1118                            char c = s.charAt(i);
1119    
1120                            // Fast path for ascii code, fallback to the slow unicode detection
1121    
1122                            if (c <= 127) {
1123                                    if ((c >= CharPool.LOWER_CASE_A) &&
1124                                            (c <= CharPool.LOWER_CASE_Z)) {
1125    
1126                                            return false;
1127                                    }
1128    
1129                                    continue;
1130                            }
1131    
1132                            if (Character.isLetter(c) && Character.isLowerCase(c)) {
1133                                    return false;
1134                            }
1135                    }
1136    
1137                    return true;
1138            }
1139    
1140            /**
1141             * Returns the index within the string of the last occurrence of any
1142             * character from the array.
1143             *
1144             * <p>
1145             * A <code>null</code> string returns <code>-1</code>. A <code>null</code>
1146             * or empty array returns <code>-1</code>.
1147             * </p>
1148             *
1149             * <p>
1150             * Examples:
1151             * </p>
1152             *
1153             * <p>
1154             * <pre>
1155             * <code>
1156             * lastIndexOfAny(null, *) returns -1
1157             * lastIndexOfAny(*, null) returns -1
1158             * lastIndexOfAny(*, []) returns -1
1159             * lastIndexOfAny("zzabyycdxx", ['a','c']) returns 6
1160             * lastIndexOfAny("zzabyycdxx", ['c','a']) returns 6
1161             * lastIndexOfAny("zzabyycdxx", ['m','n']) returns -1
1162             * </code>
1163             * </pre>
1164             * </p>
1165             *
1166             * @param  s the string to search (optionally <code>null</code>)
1167             * @param  chars the characters to search for (optionally <code>null</code>)
1168             * @return the index within the string of the last occurrence of any
1169             *         character from the array, or <code>-1</code> if none of the
1170             *         characters occur
1171             */
1172            public static int lastIndexOfAny(String s, char[] chars) {
1173                    if (s == null) {
1174                            return -1;
1175                    }
1176    
1177                    return lastIndexOfAny(s, chars, 0, s.length() - 1);
1178            }
1179    
1180            /**
1181             * Returns the index within the string of the last occurrence of any
1182             * character from the array, starting the search at the specified index
1183             * within the string.
1184             *
1185             * <p>
1186             * A <code>null</code> string returns <code>-1</code>. A <code>null</code>
1187             * or empty array returns <code>-1</code>.
1188             * </p>
1189             *
1190             * <p>
1191             * Examples:
1192             * </p>
1193             *
1194             * <p>
1195             * <pre>
1196             * <code>
1197             * lastIndexOfAny(null, *, *) returns -1
1198             * lastIndexOfAny(*, null, *) returns -1
1199             * lastIndexOfAny(*, [], *) returns -1
1200             * lastIndexOfAny("zzabyycdxx", ['a','c'], 5) returns 2
1201             * lastIndexOfAny("zzabyycdxx", ['m','n'], *) returns -1
1202             * </code>
1203             * </pre>
1204             * </p>
1205             *
1206             * @param  s the string to search (optionally <code>null</code>)
1207             * @param  chars the characters to search for (optionally <code>null</code>)
1208             * @param  toIndex the end index within the string
1209             * @return the index within the string of the last occurrence of any
1210             *         character from the array, starting the search at the specified
1211             *         index within the string, or <code>-1</code> if none of the
1212             *         characters occur
1213             */
1214            public static int lastIndexOfAny(String s, char[] chars, int toIndex) {
1215                    if (s == null) {
1216                            return -1;
1217                    }
1218    
1219                    return lastIndexOfAny(s, chars, 0, toIndex);
1220            }
1221    
1222            /**
1223             * Returns the index within the string of the last occurrence of any
1224             * character from the array, up to and including the specified end index
1225             * within the string, starting the search at the specified start index
1226             * within the string.
1227             *
1228             * <p>
1229             * A <code>null</code> string returns <code>-1</code>. A <code>null</code>
1230             * or empty array returns <code>-1</code>.
1231             * </p>
1232             *
1233             * <p>
1234             * Examples:
1235             * </p>
1236             *
1237             * <p>
1238             * <pre>
1239             * <code>
1240             * lastIndexOfAny(null</code>, *, *, *) returns -1
1241             * lastIndexOfAny(*, null</code>, *, *) returns -1
1242             * lastIndexOfAny(*, [], *, *) returns -1
1243             * lastIndexOfAny("zzabyycdxx", ['a','c'], 5, 7) returns 6
1244             * lastIndexOfAny("zzabyycdxx", ['m','n'], *, *) returns -1
1245             * </code>
1246             * </pre>
1247             * </p>
1248             *
1249             * @param  s the string to search (optionally <code>null</code>)
1250             * @param  chars the characters to search for (optionally <code>null</code>)
1251             * @param  fromIndex the start index within the string
1252             * @param  toIndex the end index within the string
1253             * @return the index within the string of the last occurrence of any
1254             *         character from the array, up to and including the specified end
1255             *         index within the string, starting the search at the specified
1256             *         start index within the string, or <code>-1</code> if none of the
1257             *         characters occur
1258             */
1259            public static int lastIndexOfAny(
1260                    String s, char[] chars, int fromIndex, int toIndex) {
1261    
1262                    if ((s == null) || (toIndex < fromIndex)) {
1263                            return -1;
1264                    }
1265    
1266                    if (ArrayUtil.isEmpty(chars)) {
1267                            return -1;
1268                    }
1269    
1270                    if (fromIndex >= s.length()) {
1271                            return -1;
1272                    }
1273    
1274                    if (fromIndex < 0) {
1275                            fromIndex = 0;
1276                    }
1277    
1278                    if (toIndex >= s.length()) {
1279                            toIndex = s.length() - 1;
1280                    }
1281    
1282                    for (int i = toIndex; i >= fromIndex; i--) {
1283                            char c = s.charAt(i);
1284    
1285                            for (int j = 0; j < chars.length; j++) {
1286                                    if (c == chars[j]) {
1287                                            return i;
1288                                    }
1289                            }
1290                    }
1291    
1292                    return -1;
1293            }
1294    
1295            /**
1296             * Returns the index within the string of the last occurrence of any string
1297             * from the array.
1298             *
1299             * <p>
1300             * A <code>null</code> string returns <code>-1</code>. A <code>null</code>
1301             * or empty array returns <code>-1</code>, but an array containing
1302             * <code>""</code> returns <code>0</code> if the string is not
1303             * <code>null</code>.
1304             * </p>
1305             *
1306             * <p>
1307             * Examples:
1308             * </p>
1309             *
1310             * <p>
1311             * <pre>
1312             * <code>
1313             * lastIndexOfAny(null</code>, *) returns -1
1314             * lastIndexOfAny(*, null</code>) returns -1
1315             * lastIndexOfAny(*, []) returns -1
1316             * lastIndexOfAny(*, [null</code>]) returns -1
1317             * lastIndexOfAny("zzabyycdxx", ["ab","cd"]) returns 6
1318             * lastIndexOfAny("zzabyycdxx", ["cd","ab"]) returns 6
1319             * lastIndexOfAny("zzabyycdxx", ["mn","op"]) returns -1
1320             * lastIndexOfAny("zzabyycdxx", ["mn",""]) returns 10
1321             * </code>
1322             * </pre>
1323             * </p>
1324             *
1325             * @param  s the string to search (optionally <code>null</code>)
1326             * @param  texts the strings to search for (optionally <code>null</code>)
1327             * @return the index within the string of the last occurrence of any string
1328             *         from the array, <code>0</code> if the search array contains
1329             *         <code>""</code>, or <code>-1</code> if none of the strings occur
1330             */
1331            public static int lastIndexOfAny(String s, String[] texts) {
1332                    if (s == null) {
1333                            return -1;
1334                    }
1335    
1336                    return lastIndexOfAny(s, texts, 0, s.length() - 1);
1337            }
1338    
1339            /**
1340             * Returns the index within the string of the last occurrence of any string
1341             * from the array, starting the search at the specified index within the
1342             * string.
1343             *
1344             * <p>
1345             * A <code>null</code> string returns <code>-1</code>. A <code>null</code>
1346             * or empty array returns <code>-1</code>, but an array containing
1347             * <code>""</code> returns the specified start index if the string is not
1348             * <code>null</code>.
1349             * </p>
1350             *
1351             * <p>
1352             * Examples:
1353             * </p>
1354             *
1355             * <p>
1356             * <pre>
1357             * <code>
1358             * lastIndexOfAny(null, *, *) returns -1
1359             * lastIndexOfAny(*, null, *) returns -1
1360             * lastIndexOfAny(*, [], *) returns -1
1361             * lastIndexOfAny(*, [null], *) returns -1
1362             * lastIndexOfAny("zzabyycdxx", ["ab","cd"], 5) returns 2
1363             * lastIndexOfAny("zzabyycdxx", ["cd","ab"], 5) returns 2
1364             * lastIndexOfAny("zzabyycdxx", ["mn","op"], *) returns -1
1365             * lastIndexOfAny("zzabyycdxx", ["mn",""], 5) returns 5
1366             * </code>
1367             * </pre>
1368             * </p>
1369             *
1370             * @param  s the string to search (optionally <code>null</code>)
1371             * @param  texts the strings to search for (optionally <code>null</code>)
1372             * @param  toIndex the end index within the string
1373             * @return the index within the string of the last occurrence of any string
1374             *         from the array, starting the search at the specified index within
1375             *         the string, the start index if the search array contains
1376             *         <code>""</code>, or <code>-1</code> if none of the strings occur
1377             */
1378            public static int lastIndexOfAny(String s, String[] texts, int toIndex) {
1379                    if (s == null) {
1380                            return -1;
1381                    }
1382    
1383                    return lastIndexOfAny(s, texts, 0, toIndex);
1384            }
1385    
1386            /**
1387             * Returns the index within the string of the last occurrence of any string
1388             * from the array, up to and including the specified end index within the
1389             * string, starting the search at the specified start index within the
1390             * string.
1391             *
1392             * <p>
1393             * A <code>null</code> string returns <code>-1</code>. A <code>null</code>
1394             * or empty array returns <code>-1</code>, but an array containing
1395             * <code>""</code> returns the specified end index if the string is not
1396             * <code>null</code>.
1397             * </p>
1398             *
1399             * <p>
1400             * Examples:
1401             * </p>
1402             *
1403             * <p>
1404             * <pre>
1405             * <code>
1406             * lastIndexOfAny(null, *, *, *) returns -1
1407             * lastIndexOfAny(*, null, *, *) returns -1
1408             * lastIndexOfAny(*, [], *, *) returns -1
1409             * lastIndexOfAny(*, [null], *, *) returns -1
1410             * lastIndexOfAny("zzabyycdxx", ["ab","cd"], 2, 5) returns 2
1411             * lastIndexOfAny("zzabyycdxx", ["mn","op"], *, *) returns -1
1412             * lastIndexOfAny("zzabyycdxx", ["mn",""], 2, 5) returns 5
1413             * </code>
1414             * </pre>
1415             * </p>
1416             *
1417             * @param  s the string to search (optionally <code>null</code>)
1418             * @param  texts the strings to search for (optionally <code>null</code>)
1419             * @param  fromIndex the start index within the string
1420             * @param  toIndex the end index within the string
1421             * @return the index within the string of the last occurrence of any string
1422             *         from the array, up to and including the specified end index
1423             *         within the string, starting the search at the specified start
1424             *         index within the string, the end index if the search array
1425             *         contains <code>""</code>, or <code>-1</code> if none of the
1426             *         strings occur
1427             */
1428            public static int lastIndexOfAny(
1429                    String s, String[] texts, int fromIndex, int toIndex) {
1430    
1431                    if ((s == null) || (toIndex < fromIndex)) {
1432                            return -1;
1433                    }
1434    
1435                    if (ArrayUtil.isEmpty(texts)) {
1436                            return -1;
1437                    }
1438    
1439                    if (fromIndex >= s.length()) {
1440                            return -1;
1441                    }
1442    
1443                    if (fromIndex < 0) {
1444                            fromIndex = 0;
1445                    }
1446    
1447                    if (toIndex >= s.length()) {
1448                            toIndex = s.length() - 1;
1449                    }
1450    
1451                    for (int i = toIndex; i >= fromIndex; i--) {
1452                            for (int j = 0; j < texts.length; j++) {
1453                                    if (texts[j] == null) {
1454                                            continue;
1455                                    }
1456    
1457                                    if ((i + texts[j].length() <= toIndex + 1) &&
1458                                            s.startsWith(texts[j], i)) {
1459    
1460                                            return i;
1461                                    }
1462                            }
1463                    }
1464    
1465                    return -1;
1466            }
1467    
1468            /**
1469             * Converts all of the characters in the string to lower case.
1470             *
1471             * @param  s the string to convert
1472             * @return the string, converted to lower case, or <code>null</code> if the
1473             *         string is <code>null</code>
1474             * @see    String#toLowerCase()
1475             */
1476            public static String lowerCase(String s) {
1477                    return toLowerCase(s);
1478            }
1479    
1480            /**
1481             * Converts all of the characters in the arbitrary number of strings to
1482             * lower case.
1483             *
1484             * @param array the array or sequence of string arguments
1485             */
1486            public static void lowerCase(String... array) {
1487                    if (array != null) {
1488                            for (int i = 0; i < array.length; i++) {
1489                                    array[i] = toLowerCase(array[i]);
1490                            }
1491                    }
1492            }
1493    
1494            /**
1495             * Converts the first character of the string to lower case.
1496             *
1497             * @param  s the string whose first character is to be converted
1498             * @return the string, with its first character converted to lower-case
1499             */
1500            public static String lowerCaseFirstLetter(String s) {
1501                    char[] chars = s.toCharArray();
1502    
1503                    if ((chars[0] >= 65) && (chars[0] <= 90)) {
1504                            chars[0] = (char)(chars[0] + 32);
1505                    }
1506    
1507                    return new String(chars);
1508            }
1509    
1510            /**
1511             * Returns <code>true</code> if the specified pattern occurs at any position
1512             * in the string.
1513             *
1514             * @param  s the string
1515             * @param  pattern the pattern to search for in the string
1516             * @return <code>true</code> if the specified pattern occurs at any position
1517             *         in the string
1518             */
1519            public static boolean matches(String s, String pattern) {
1520                    String[] array = pattern.split("\\*");
1521    
1522                    for (String element : array) {
1523                            int pos = s.indexOf(element);
1524    
1525                            if (pos == -1) {
1526                                    return false;
1527                            }
1528    
1529                            s = s.substring(pos + element.length());
1530                    }
1531    
1532                    return true;
1533            }
1534    
1535            /**
1536             * Returns <code>true</code> if the specified pattern occurs at any position
1537             * in the string, ignoring case.
1538             *
1539             * @param  s the string
1540             * @param  pattern the pattern to search for in the string
1541             * @return <code>true</code> if the specified pattern occurs at any position
1542             *         in the string
1543             */
1544            public static boolean matchesIgnoreCase(String s, String pattern) {
1545                    return matches(lowerCase(s), lowerCase(pattern));
1546            }
1547    
1548            /**
1549             * Merges the elements of the boolean array into a string representing a
1550             * comma delimited list of its values.
1551             *
1552             * @param  array the boolean values to merge
1553             * @return a string representing a comma delimited list of the values of the
1554             *         boolean array, an empty string if the array is empty, or
1555             *         <code>null</code> if the array is <code>null</code>
1556             */
1557            public static String merge(boolean[] array) {
1558                    return merge(array, StringPool.COMMA);
1559            }
1560    
1561            /**
1562             * Merges the elements of the boolean array into a string representing a
1563             * delimited list of its values.
1564             *
1565             * @param  array the boolean values to merge
1566             * @param  delimiter the delimiter
1567             * @return a string representing a comma delimited list of the values of the
1568             *         boolean array, an empty string if the array is empty, or
1569             *         <code>null</code> if the array is <code>null</code>
1570             */
1571            public static String merge(boolean[] array, String delimiter) {
1572                    if (array == null) {
1573                            return null;
1574                    }
1575    
1576                    if (array.length == 0) {
1577                            return StringPool.BLANK;
1578                    }
1579    
1580                    StringBundler sb = new StringBundler(2 * array.length - 1);
1581    
1582                    for (int i = 0; i < array.length; i++) {
1583                            if (i != 0) {
1584                                    sb.append(delimiter);
1585                            }
1586    
1587                            sb.append(String.valueOf(array[i]));
1588                    }
1589    
1590                    return sb.toString();
1591            }
1592    
1593            /**
1594             * Merges the elements of the character array into a string representing a
1595             * comma delimited list of its values.
1596             *
1597             * @param  array the characters to merge
1598             * @return a string representing a comma delimited list of the values of the
1599             *         character array, an empty string if the array is empty, or
1600             *         <code>null</code> if the array is <code>null</code>
1601             */
1602            public static String merge(char[] array) {
1603                    return merge(array, StringPool.COMMA);
1604            }
1605    
1606            /**
1607             * Merges the elements of the character array into a string representing a
1608             * delimited list of its values.
1609             *
1610             * @param  array the characters to merge
1611             * @param  delimiter the delimiter
1612             * @return a string representing a delimited list of the values of the
1613             *         character array, an empty string if the array is empty, or
1614             *         <code>null</code> if the array is <code>null</code>
1615             */
1616            public static String merge(char[] array, String delimiter) {
1617                    if (array == null) {
1618                            return null;
1619                    }
1620    
1621                    if (array.length == 0) {
1622                            return StringPool.BLANK;
1623                    }
1624    
1625                    StringBundler sb = new StringBundler(2 * array.length - 1);
1626    
1627                    for (int i = 0; i < array.length; i++) {
1628                            if (i != 0) {
1629                                    sb.append(delimiter);
1630                            }
1631    
1632                            sb.append(String.valueOf(array[i]));
1633                    }
1634    
1635                    return sb.toString();
1636            }
1637    
1638            /**
1639             * Merges the elements of the collection by returning a string representing
1640             * a comma delimited list of its values.
1641             *
1642             * @param  col the collection of objects
1643             * @return the merged collection elements, or <code>null</code> if the
1644             *         collection is <code>null</code>
1645             */
1646            public static String merge(Collection<?> col) {
1647                    return merge(col, StringPool.COMMA);
1648            }
1649    
1650            /**
1651             * Merges the elements of the collection by returning a string representing
1652             * a delimited list of its values.
1653             *
1654             * @param  col the collection of objects
1655             * @param  delimiter the string whose last index in the string marks where
1656             *         to begin the substring
1657             * @return the merged collection elements, or <code>null</code> if the
1658             *         collection is <code>null</code>
1659             */
1660            public static String merge(Collection<?> col, String delimiter) {
1661                    if (col == null) {
1662                            return null;
1663                    }
1664    
1665                    return merge(col.toArray(new Object[col.size()]), delimiter);
1666            }
1667    
1668            /**
1669             * Merges the elements of an array of double-precision decimal numbers by
1670             * returning a string representing a comma delimited list of its values.
1671             *
1672             * @param  array the doubles to merge
1673             * @return a string representing a comma delimited list of the values of the
1674             *         array of double-precision decimal numbers, an empty string if the
1675             *         array is empty, or <code>null</code> if the array is
1676             *         <code>null</code>
1677             */
1678            public static String merge(double[] array) {
1679                    return merge(array, StringPool.COMMA);
1680            }
1681    
1682            /**
1683             * Merges the elements of an array of double-precision decimal numbers by
1684             * returning a string representing a delimited list of its values.
1685             *
1686             * @param  array the doubles to merge
1687             * @param  delimiter the delimiter
1688             * @return a string representing a delimited list of the values of the array
1689             *         of double-precision decimal numbers, an empty string if the array
1690             *         is empty, or <code>null</code> if the array is <code>null</code>
1691             */
1692            public static String merge(double[] array, String delimiter) {
1693                    if (array == null) {
1694                            return null;
1695                    }
1696    
1697                    if (array.length == 0) {
1698                            return StringPool.BLANK;
1699                    }
1700    
1701                    StringBundler sb = new StringBundler(2 * array.length - 1);
1702    
1703                    for (int i = 0; i < array.length; i++) {
1704                            if (i != 0) {
1705                                    sb.append(delimiter);
1706                            }
1707    
1708                            sb.append(String.valueOf(array[i]));
1709                    }
1710    
1711                    return sb.toString();
1712            }
1713    
1714            /**
1715             * Merges the elements of an array of decimal numbers into a string
1716             * representing a comma delimited list of its values.
1717             *
1718             * @param  array the floats to merge
1719             * @return a string representing a comma delimited list of the values of the
1720             *         array of decimal numbers, an empty string if the array is empty,
1721             *         or <code>null</code> if the array is <code>null</code>
1722             */
1723            public static String merge(float[] array) {
1724                    return merge(array, StringPool.COMMA);
1725            }
1726    
1727            /**
1728             * Merges the elements of an array of decimal numbers into a string
1729             * representing a delimited list of its values.
1730             *
1731             * @param  array the floats to merge
1732             * @param  delimiter the delimiter
1733             * @return a string representing a delimited list of the values of the array
1734             *         of decimal numbers, an empty string if the array is empty, or
1735             *         <code>null</code> if the array is <code>null</code>
1736             */
1737            public static String merge(float[] array, String delimiter) {
1738                    if (array == null) {
1739                            return null;
1740                    }
1741    
1742                    if (array.length == 0) {
1743                            return StringPool.BLANK;
1744                    }
1745    
1746                    StringBundler sb = new StringBundler(2 * array.length - 1);
1747    
1748                    for (int i = 0; i < array.length; i++) {
1749                            if (i != 0) {
1750                                    sb.append(delimiter);
1751                            }
1752    
1753                            sb.append(String.valueOf(array[i]));
1754                    }
1755    
1756                    return sb.toString();
1757            }
1758    
1759            /**
1760             * Merges the elements of an array of integers into a string representing a
1761             * comma delimited list of its values.
1762             *
1763             * @param  array the integers to merge
1764             * @return a string representing a comma delimited list of the values of the
1765             *         array of integers, an empty string if the array is empty, or
1766             *         <code>null</code> if the array is <code>null</code>
1767             */
1768            public static String merge(int[] array) {
1769                    return merge(array, StringPool.COMMA);
1770            }
1771    
1772            /**
1773             * Merges the elements of an array of integers into a string representing a
1774             * delimited list of its values.
1775             *
1776             * @param  array the integers to merge
1777             * @param  delimiter the delimiter
1778             * @return a string representing a delimited list of the values of the array
1779             *         of integers, an empty string if the array is empty, or
1780             *         <code>null</code> if the array is <code>null</code>
1781             */
1782            public static String merge(int[] array, String delimiter) {
1783                    if (array == null) {
1784                            return null;
1785                    }
1786    
1787                    if (array.length == 0) {
1788                            return StringPool.BLANK;
1789                    }
1790    
1791                    StringBundler sb = new StringBundler(2 * array.length - 1);
1792    
1793                    for (int i = 0; i < array.length; i++) {
1794                            if (i != 0) {
1795                                    sb.append(delimiter);
1796                            }
1797    
1798                            sb.append(String.valueOf(array[i]));
1799                    }
1800    
1801                    return sb.toString();
1802            }
1803    
1804            /**
1805             * Merges the elements of an array of long integers by returning a string
1806             * representing a comma delimited list of its values.
1807             *
1808             * @param  array the long integers to merge
1809             * @return a string representing a comma delimited list of the values of the
1810             *         array of long integers, an empty string if the array is empty, or
1811             *         <code>null</code> if the array is <code>null</code>
1812             */
1813            public static String merge(long[] array) {
1814                    return merge(array, StringPool.COMMA);
1815            }
1816    
1817            /**
1818             * Merges the elements of an array of long integers by returning a string
1819             * representing a delimited list of its values.
1820             *
1821             * @param  array the long integers to merge
1822             * @param  delimiter the delimiter
1823             * @return a string representing a delimited list of the values of the array
1824             *         of long integers, an empty string if the array is empty, or
1825             *         <code>null</code> if the array is <code>null</code>
1826             */
1827            public static String merge(long[] array, String delimiter) {
1828                    if (array == null) {
1829                            return null;
1830                    }
1831    
1832                    if (array.length == 0) {
1833                            return StringPool.BLANK;
1834                    }
1835    
1836                    StringBundler sb = new StringBundler(2 * array.length - 1);
1837    
1838                    for (int i = 0; i < array.length; i++) {
1839                            if (i != 0) {
1840                                    sb.append(delimiter);
1841                            }
1842    
1843                            sb.append(String.valueOf(array[i]));
1844                    }
1845    
1846                    return sb.toString();
1847            }
1848    
1849            /**
1850             * Merges the elements of an array of objects into a string representing a
1851             * comma delimited list of the objects.
1852             *
1853             * @param  array the objects to merge
1854             * @return a string representing a comma delimited list of the objects, an
1855             *         empty string if the array is empty, or <code>null</code> if the
1856             *         array is <code>null</code>
1857             */
1858            public static String merge(Object[] array) {
1859                    return merge(array, StringPool.COMMA);
1860            }
1861    
1862            /**
1863             * Merges the elements of an array of objects into a string representing a
1864             * delimited list of the objects.
1865             *
1866             * @param  array the objects to merge
1867             * @param  delimiter the delimiter
1868             * @return a string representing a delimited list of the objects, an empty
1869             *         string if the array is empty, or <code>null</code> if the array
1870             *         is <code>null</code>
1871             */
1872            public static String merge(Object[] array, String delimiter) {
1873                    if (array == null) {
1874                            return null;
1875                    }
1876    
1877                    if (array.length == 0) {
1878                            return StringPool.BLANK;
1879                    }
1880    
1881                    StringBundler sb = new StringBundler(2 * array.length - 1);
1882    
1883                    for (int i = 0; i < array.length; i++) {
1884                            if (i != 0) {
1885                                    sb.append(delimiter);
1886                            }
1887    
1888                            sb.append(String.valueOf(array[i]).trim());
1889                    }
1890    
1891                    return sb.toString();
1892            }
1893    
1894            /**
1895             * Merges the elements of an array of short integers by returning a string
1896             * representing a comma delimited list of its values.
1897             *
1898             * @param  array the short integers to merge
1899             * @return a string representing a comma delimited list of the values of the
1900             *         array of short integers, an empty string if the array is empty,
1901             *         or <code>null</code> if the array is <code>null</code>
1902             */
1903            public static String merge(short[] array) {
1904                    return merge(array, StringPool.COMMA);
1905            }
1906    
1907            /**
1908             * Merges the elements of an array of short integers by returning a string
1909             * representing a delimited list of its values.
1910             *
1911             * @param  array the short integers to merge
1912             * @param  delimiter the delimiter
1913             * @return a string representing a delimited list of the values of the array
1914             *         of short integers, an empty string if the array is empty, or
1915             *         <code>null</code> if the array is <code>null</code>
1916             */
1917            public static String merge(short[] array, String delimiter) {
1918                    if (array == null) {
1919                            return null;
1920                    }
1921    
1922                    if (array.length == 0) {
1923                            return StringPool.BLANK;
1924                    }
1925    
1926                    StringBundler sb = new StringBundler(2 * array.length - 1);
1927    
1928                    for (int i = 0; i < array.length; i++) {
1929                            if (i != 0) {
1930                                    sb.append(delimiter);
1931                            }
1932    
1933                            sb.append(String.valueOf(array[i]));
1934                    }
1935    
1936                    return sb.toString();
1937            }
1938    
1939            /**
1940             * Returns the string enclosed by apostrophes.
1941             *
1942             * <p>
1943             * Example:
1944             * </p>
1945             *
1946             * <p>
1947             * <pre>
1948             * <code>
1949             * quote("Hello, World!") returns "'Hello, World!'"
1950             * </code>
1951             * </pre>
1952             * </p>
1953             *
1954             * @param  s the string to enclose in apostrophes
1955             * @return the string enclosed by apostrophes, or <code>null</code> if the
1956             *         string is <code>null</code>
1957             */
1958            public static String quote(String s) {
1959                    return quote(s, CharPool.APOSTROPHE);
1960            }
1961    
1962            /**
1963             * Returns the string enclosed by the quote character.
1964             *
1965             * <p>
1966             * Example:
1967             * </p>
1968             *
1969             * <p>
1970             * <pre>
1971             * <code>
1972             * quote("PATH", '%') returns "%PATH%"
1973             * </code>
1974             * </pre>
1975             * </p>
1976             *
1977             * @param  s the string to enclose in quotes
1978             * @param  quote the character to insert to insert to the beginning of and
1979             *         append to the end of the string
1980             * @return the string enclosed in the quote characters, or <code>null</code>
1981             *         if the string is <code>null</code>
1982             */
1983            public static String quote(String s, char quote) {
1984                    if (s == null) {
1985                            return null;
1986                    }
1987    
1988                    return quote(s, String.valueOf(quote));
1989            }
1990    
1991            /**
1992             * Returns the string enclosed by the quote strings.
1993             *
1994             * <p>
1995             * Example:
1996             * </p>
1997             *
1998             * <p>
1999             * <pre>
2000             * <code>
2001             * quote("WARNING", "!!!") returns "!!!WARNING!!!"
2002             * </code>
2003             * </pre>
2004             * </p>
2005             *
2006             * @param  s the string to enclose in quotes
2007             * @param  quote the quote string to insert to insert to the beginning of
2008             *         and append to the end of the string
2009             * @return the string enclosed in the quote strings, or <code>null</code> if
2010             *         the string is <code>null</code>
2011             */
2012            public static String quote(String s, String quote) {
2013                    if (s == null) {
2014                            return null;
2015                    }
2016    
2017                    return quote.concat(s).concat(quote);
2018            }
2019    
2020            /**
2021             * Returns a randomized string of four lower case, alphabetic characters.
2022             *
2023             * @return a randomized string of four lower case, alphabetic characters
2024             */
2025            public static String randomId() {
2026                    Random random = new Random();
2027    
2028                    char[] chars = new char[4];
2029    
2030                    for (int i = 0; i < 4; i++) {
2031                            chars[i] = (char)(CharPool.LOWER_CASE_A + random.nextInt(26));
2032                    }
2033    
2034                    return new String(chars);
2035            }
2036    
2037            /**
2038             * Pseudorandomly permutes the characters of the string.
2039             *
2040             * @param  s the string whose characters are to be randomized
2041             * @return a string of the same length as the string whose characters
2042             *         represent a pseudorandom permutation of the characters of the
2043             *         string
2044             */
2045            public static String randomize(String s) {
2046                    return RandomUtil.shuffle(s);
2047            }
2048    
2049            /**
2050             * Returns a randomized string of eight characters consisting of lower case
2051             * letters, upper case letters, and single-digit whole numbers.
2052             *
2053             * @return a randomized string of eight characters consisting of lower case
2054             *         letters, upper case letters, and single-digit whole numbers
2055             */
2056            public static String randomString() {
2057                    return randomString(8);
2058            }
2059    
2060            /**
2061             * Returns a randomized string of the specified length consisting of lower
2062             * case letters, upper case letters, and single-digit whole numbers.
2063             *
2064             * @param  length the character length of the randomized string
2065             * @return a randomized string of the specified length consisting of lower
2066             *         case letters, upper case letters, and single-digit whole numbers
2067             */
2068            public static String randomString(int length) {
2069                    Random random = new Random();
2070    
2071                    char[] chars = new char[length];
2072    
2073                    for (int i = 0; i < length; i++) {
2074                            int index = random.nextInt(_RANDOM_STRING_CHAR_TABLE.length);
2075    
2076                            chars[i] = _RANDOM_STRING_CHAR_TABLE[index];
2077                    }
2078    
2079                    return new String(chars);
2080            }
2081    
2082            public static String read(ClassLoader classLoader, String name)
2083                    throws IOException {
2084    
2085                    return read(classLoader, name, false);
2086            }
2087    
2088            public static String read(ClassLoader classLoader, String name, boolean all)
2089                    throws IOException {
2090    
2091                    if (all) {
2092                            StringBundler sb = new StringBundler();
2093    
2094                            Enumeration<URL> enu = classLoader.getResources(name);
2095    
2096                            while (enu.hasMoreElements()) {
2097                                    URL url = enu.nextElement();
2098    
2099                                    InputStream is = url.openStream();
2100    
2101                                    if (is == null) {
2102                                            throw new IOException(
2103                                                    "Unable to open resource at " + url.toString());
2104                                    }
2105    
2106                                    try {
2107                                            String s = read(is);
2108    
2109                                            if (s != null) {
2110                                                    sb.append(s);
2111                                                    sb.append(StringPool.NEW_LINE);
2112                                            }
2113                                    }
2114                                    finally {
2115                                            StreamUtil.cleanUp(is);
2116                                    }
2117                            }
2118    
2119                            return sb.toString().trim();
2120                    }
2121    
2122                    InputStream is = classLoader.getResourceAsStream(name);
2123    
2124                    if (is == null) {
2125                            throw new IOException(
2126                                    "Unable to open resource in class loader " + name);
2127                    }
2128    
2129                    try {
2130                            String s = read(is);
2131    
2132                            return s;
2133                    }
2134                    finally {
2135                            StreamUtil.cleanUp(is);
2136                    }
2137            }
2138    
2139            public static String read(InputStream is) throws IOException {
2140                    StringBundler sb = new StringBundler();
2141    
2142                    try (UnsyncBufferedReader unsyncBufferedReader =
2143                                    new UnsyncBufferedReader(new InputStreamReader(is))) {
2144    
2145                            String line = null;
2146    
2147                            while ((line = unsyncBufferedReader.readLine()) != null) {
2148                                    sb.append(line);
2149                                    sb.append(CharPool.NEW_LINE);
2150                            }
2151                    }
2152    
2153                    return sb.toString().trim();
2154            }
2155    
2156            public static void readLines(InputStream is, Collection<String> lines)
2157                    throws IOException {
2158    
2159                    try (UnsyncBufferedReader unsyncBufferedReader =
2160                                    new UnsyncBufferedReader(new InputStreamReader(is))) {
2161    
2162                            String line = null;
2163    
2164                            while ((line = unsyncBufferedReader.readLine()) != null) {
2165                                    lines.add(line);
2166                            }
2167                    }
2168            }
2169    
2170            /**
2171             * @deprecated As of 7.0.0, replaced by {@link #removeFromList(String,
2172             *             String)}
2173             */
2174            @Deprecated
2175            public static String remove(String s, String element) {
2176                    return removeFromList(s, element, StringPool.COMMA);
2177            }
2178    
2179            /**
2180             * @deprecated As of 7.0.0, replaced by {@link #removeFromList(String,
2181             *             String, String)}
2182             */
2183            @Deprecated
2184            public static String remove(String s, String element, String delimiter) {
2185                    return removeFromList(s, element, delimiter);
2186            }
2187    
2188            /**
2189             * Removes the <code>remove</code> string from string <code>s</code> that
2190             * represents a list of comma delimited strings.
2191             *
2192             * <p>
2193             * The resulting string ends with a comma even if the original string does
2194             * not.
2195             * </p>
2196             *
2197             * <p>
2198             * Examples:
2199             * </p>
2200             *
2201             * <p>
2202             * <pre>
2203             * <code>
2204             * remove("red,blue,green,yellow", "blue") returns "red,green,yellow,"
2205             * remove("blue", "blue") returns ""
2206             * remove("blue,", "blue") returns ""
2207             * </code>
2208             * </pre>
2209             * </p>
2210             *
2211             * @param  s the string representing the list of comma delimited strings
2212             * @param  element the string to remove
2213             * @return a string representing the list of comma delimited strings with
2214             *         the <code>remove</code> string removed, or <code>null</code> if
2215             *         the original string, the string to remove, or the delimiter is
2216             *         <code>null</code>
2217             */
2218            public static String removeFromList(String s, String element) {
2219                    return removeFromList(s, element, StringPool.COMMA);
2220            }
2221    
2222            /**
2223             * Removes the <code>remove</code> string from string <code>s</code> that
2224             * represents a list of delimited strings.
2225             *
2226             * <p>
2227             * The resulting string ends with the delimiter even if the original string
2228             * does not.
2229             * </p>
2230             *
2231             * <p>
2232             * Examples:
2233             * </p>
2234             *
2235             * <p>
2236             * <pre>
2237             * <code>
2238             * remove("red;blue;green;yellow", "blue", ";") returns "red;green;yellow;"
2239             * remove("blue", "blue", ";") returns ""
2240             * remove("blue;", "blue", ";") returns ""
2241             * </code>
2242             * </pre>
2243             * </p>
2244             *
2245             * @param  s the string representing the list of delimited strings
2246             * @param  element the string to remove
2247             * @param  delimiter the delimiter
2248             * @return a string representing the list of delimited strings with the
2249             *         <code>remove</code> string removed, or <code>null</code> if the
2250             *         original string, the string to remove, or the delimiter is
2251             *         <code>null</code>
2252             */
2253            public static String removeFromList(
2254                    String s, String element, String delimiter) {
2255    
2256                    if ((s == null) || (element == null) || (delimiter == null)) {
2257                            return null;
2258                    }
2259    
2260                    if (Validator.isNotNull(s) && !s.endsWith(delimiter)) {
2261                            s += delimiter;
2262                    }
2263    
2264                    String drd = delimiter.concat(element).concat(delimiter);
2265    
2266                    String rd = element.concat(delimiter);
2267    
2268                    while (contains(s, element, delimiter)) {
2269                            int pos = s.indexOf(drd);
2270    
2271                            if (pos == -1) {
2272                                    if (s.startsWith(rd)) {
2273                                            int x = element.length() + delimiter.length();
2274                                            int y = s.length();
2275    
2276                                            s = s.substring(x, y);
2277                                    }
2278                            }
2279                            else {
2280                                    int x = pos + element.length() + delimiter.length();
2281                                    int y = s.length();
2282    
2283                                    String temp = s.substring(0, pos);
2284    
2285                                    s = temp.concat(s.substring(x, y));
2286                            }
2287                    }
2288    
2289                    return s;
2290            }
2291    
2292            /**
2293             * Replaces all occurrences of the character with the new character.
2294             *
2295             * @param  s the original string
2296             * @param  oldSub the character to be searched for and replaced in the
2297             *         original string
2298             * @param  newSub the character with which to replace the
2299             *         <code>oldSub</code> character
2300             * @return a string representing the original string with all occurrences of
2301             *         the <code>oldSub</code> character replaced with the
2302             *         <code>newSub</code> character, or <code>null</code> if the
2303             *         original string is <code>null</code>
2304             */
2305            public static String replace(String s, char oldSub, char newSub) {
2306                    if (s == null) {
2307                            return null;
2308                    }
2309    
2310                    return s.replace(oldSub, newSub);
2311            }
2312    
2313            /**
2314             * Replaces all occurrences of the character with the new string.
2315             *
2316             * @param  s the original string
2317             * @param  oldSub the character to be searched for and replaced in the
2318             *         original string
2319             * @param  newSub the string with which to replace the <code>oldSub</code>
2320             *         character
2321             * @return a string representing the original string with all occurrences of
2322             *         the <code>oldSub</code> character replaced with the string
2323             *         <code>newSub</code>, or <code>null</code> if the original string
2324             *         is <code>null</code>
2325             */
2326            public static String replace(String s, char oldSub, String newSub) {
2327                    if ((s == null) || (newSub == null)) {
2328                            return null;
2329                    }
2330    
2331                    // The number 5 is arbitrary and is used as extra padding to reduce
2332                    // buffer expansion
2333    
2334                    StringBundler sb = new StringBundler(s.length() + 5 * newSub.length());
2335    
2336                    char[] chars = s.toCharArray();
2337    
2338                    for (char c : chars) {
2339                            if (c == oldSub) {
2340                                    sb.append(newSub);
2341                            }
2342                            else {
2343                                    sb.append(c);
2344                            }
2345                    }
2346    
2347                    return sb.toString();
2348            }
2349    
2350            /**
2351             * Replaces all occurrences of the string with the new string.
2352             *
2353             * @param  s the original string
2354             * @param  oldSub the string to be searched for and replaced in the original
2355             *         string
2356             * @param  newSub the string with which to replace the <code>oldSub</code>
2357             *         string
2358             * @return a string representing the original string with all occurrences of
2359             *         the <code>oldSub</code> string replaced with the string
2360             *         <code>newSub</code>, or <code>null</code> if the original string
2361             *         is <code>null</code>
2362             */
2363            public static String replace(String s, String oldSub, String newSub) {
2364                    return replace(s, oldSub, newSub, 0);
2365            }
2366    
2367            /**
2368             * Replaces all occurrences of the string with the new string, starting from
2369             * the specified index.
2370             *
2371             * @param  s the original string
2372             * @param  oldSub the string to be searched for and replaced in the original
2373             *         string
2374             * @param  newSub the string with which to replace the <code>oldSub</code>
2375             *         string
2376             * @param  fromIndex the index of the original string from which to begin
2377             *         searching
2378             * @return a string representing the original string with all occurrences of
2379             *         the <code>oldSub</code> string occurring after the specified
2380             *         index replaced with the string <code>newSub</code>, or
2381             *         <code>null</code> if the original string is <code>null</code>
2382             */
2383            public static String replace(
2384                    String s, String oldSub, String newSub, int fromIndex) {
2385    
2386                    if (s == null) {
2387                            return null;
2388                    }
2389    
2390                    if ((oldSub == null) || oldSub.equals(StringPool.BLANK)) {
2391                            return s;
2392                    }
2393    
2394                    if (newSub == null) {
2395                            newSub = StringPool.BLANK;
2396                    }
2397    
2398                    int y = s.indexOf(oldSub, fromIndex);
2399    
2400                    if (y >= 0) {
2401                            StringBundler sb = new StringBundler();
2402    
2403                            int length = oldSub.length();
2404                            int x = 0;
2405    
2406                            while (x <= y) {
2407                                    sb.append(s.substring(x, y));
2408                                    sb.append(newSub);
2409    
2410                                    x = y + length;
2411                                    y = s.indexOf(oldSub, x);
2412                            }
2413    
2414                            sb.append(s.substring(x));
2415    
2416                            return sb.toString();
2417                    }
2418                    else {
2419                            return s;
2420                    }
2421            }
2422    
2423            /**
2424             * Replaces all occurrences of the keywords found in the substring, defined
2425             * by the beginning and ending strings, with the new values.
2426             *
2427             * <p>
2428             * For example, with the following initialized variables:
2429             * </p>
2430             *
2431             * <p>
2432             * <pre>
2433             * <code>
2434             * String s = "http://www.example-url/${userId}";
2435             * String begin = "${";
2436             * String end = "}";
2437             * Map<String, String> values =  new HashMap&#60;String, String&#62;();
2438             * values.put("userId", "jbloggs");
2439             * </code>
2440             * </pre>
2441             * </p>
2442             *
2443             * <p>
2444             * <code>replace(s, begin, end, values)</code> returns
2445             * <code>"http://www.example-url/jbloggs"</code>
2446             * </p>
2447             *
2448             * @param  s the original string
2449             * @param  begin the string preceding the substring to be modified. This
2450             *         string is excluded from the result.
2451             * @param  end the string following the substring to be modified. This
2452             *         string is excluded from the result.
2453             * @param  values the key-value map values
2454             * @return a string representing the original string with all occurrences of
2455             *         the of the keywords found in the substring, replaced with the new
2456             *         values. <code>null</code> is returned if the original string, the
2457             *         beginning string, the ending string, or the key-map values are
2458             *         <code>null</code>.
2459             */
2460            public static String replace(
2461                    String s, String begin, String end, Map<String, String> values) {
2462    
2463                    StringBundler sb = replaceToStringBundler(s, begin, end, values);
2464    
2465                    return sb.toString();
2466            }
2467    
2468            /**
2469             * Replaces all occurrences of the elements of the string array with the
2470             * corresponding elements of the new string array.
2471             *
2472             * @param  s the original string
2473             * @param  oldSubs the strings to be searched for and replaced in the
2474             *         original string
2475             * @param  newSubs the strings with which to replace the
2476             *         <code>oldSubs</code> strings
2477             * @return a string representing the original string with all occurrences of
2478             *         the <code>oldSubs</code> strings replaced with the corresponding
2479             *         <code>newSubs</code> strings, or <code>null</code> if the
2480             *         original string, the <code>oldSubs</code> array, or the
2481             *         <code>newSubs</code> is <code>null</code>
2482             */
2483            public static String replace(String s, String[] oldSubs, String[] newSubs) {
2484                    if ((s == null) || (oldSubs == null) || (newSubs == null)) {
2485                            return null;
2486                    }
2487    
2488                    if (oldSubs.length != newSubs.length) {
2489                            return s;
2490                    }
2491    
2492                    for (int i = 0; i < oldSubs.length; i++) {
2493                            s = replace(s, oldSubs[i], newSubs[i]);
2494                    }
2495    
2496                    return s;
2497            }
2498    
2499            /**
2500             * Replaces all occurrences of the elements of the string array with the
2501             * corresponding elements of the new string array, optionally replacing only
2502             * substrings that are surrounded by word boundaries.
2503             *
2504             * <p>
2505             * Examples:
2506             * </p>
2507             *
2508             * <p>
2509             * <pre>
2510             * <code>
2511             * replace("redorangeyellow", {"red", "orange", "yellow"}, {"RED","ORANGE", "YELLOW"}, false) returns "REDORANGEYELLOW"
2512             * replace("redorangeyellow", {"red", "orange", "yellow"}, {"RED","ORANGE", "YELLOW"}, true) returns "redorangeyellow"
2513             * replace("redorange yellow", {"red", "orange", "yellow"}, {"RED","ORANGE", "YELLOW"}, false) returns "REDORANGE YELLOW"
2514             * replace("redorange yellow", {"red", "orange", "yellow"}, {"RED","ORANGE", "YELLOW"}, true) returns "redorange YELLOW"
2515             * replace("red orange yellow", {"red", "orange", "yellow"}, {"RED","ORANGE", "YELLOW"}, false) returns "RED ORANGE YELLOW"
2516             * replace("redorange.yellow", {"red", "orange", "yellow"}, {"RED","ORANGE", * "YELLOW"}, true) returns "redorange.YELLOW"
2517             * </code>
2518             * </pre>
2519             * </p>
2520             *
2521             * @param  s the original string
2522             * @param  oldSubs the strings to be searched for and replaced in the
2523             *         original string
2524             * @param  newSubs the strings with which to replace the
2525             *         <code>oldSubs</code> strings
2526             * @param  exactMatch whether or not to replace only substrings of
2527             *         <code>s</code> that are surrounded by word boundaries
2528             * @return if <code>exactMatch</code> is <code>true</code>, a string
2529             *         representing the original string with all occurrences of the
2530             *         <code>oldSubs</code> strings that are surrounded by word
2531             *         boundaries replaced with the corresponding <code>newSubs</code>
2532             *         strings, or else a string representing the original string with
2533             *         all occurrences of the <code>oldSubs</code> strings replaced with
2534             *         the corresponding <code>newSubs</code> strings, or
2535             *         <code>null</code> if the original string, the
2536             *         <code>oldSubs</code> array, or the <code>newSubs</code> is
2537             *         <code>null</code>
2538             */
2539            public static String replace(
2540                    String s, String[] oldSubs, String[] newSubs, boolean exactMatch) {
2541    
2542                    if ((s == null) || (oldSubs == null) || (newSubs == null)) {
2543                            return null;
2544                    }
2545    
2546                    if (oldSubs.length != newSubs.length) {
2547                            return s;
2548                    }
2549    
2550                    if (!exactMatch) {
2551                            return replace(s, oldSubs, newSubs);
2552                    }
2553    
2554                    for (int i = 0; i < oldSubs.length; i++) {
2555                            s = s.replaceAll("\\b" + oldSubs[i] + "\\b", newSubs[i]);
2556                    }
2557    
2558                    return s;
2559            }
2560    
2561            /**
2562             * Replaces the first occurrence of the character with the new character.
2563             *
2564             * @param  s the original string
2565             * @param  oldSub the character whose first occurrence in the original
2566             *         string is to be searched for and replaced
2567             * @param  newSub the character with which to replace the first occurrence
2568             *         of the <code>oldSub</code> character
2569             * @return a string representing the original string except with the first
2570             *         occurrence of the character <code>oldSub</code> replaced with the
2571             *         character <code>newSub</code>
2572             */
2573            public static String replaceFirst(String s, char oldSub, char newSub) {
2574                    if (s == null) {
2575                            return null;
2576                    }
2577    
2578                    return replaceFirst(s, String.valueOf(oldSub), String.valueOf(newSub));
2579            }
2580    
2581            /**
2582             * Replaces the first occurrence of the character with the new string.
2583             *
2584             * @param  s the original string
2585             * @param  oldSub the character whose first occurrence in the original
2586             *         string is to be searched for and replaced
2587             * @param  newSub the string with which to replace the first occurrence of
2588             *         the <code>oldSub</code> character
2589             * @return a string representing the original string except with the first
2590             *         occurrence of the character <code>oldSub</code> replaced with the
2591             *         string <code>newSub</code>
2592             */
2593            public static String replaceFirst(String s, char oldSub, String newSub) {
2594                    if ((s == null) || (newSub == null)) {
2595                            return null;
2596                    }
2597    
2598                    return replaceFirst(s, String.valueOf(oldSub), newSub);
2599            }
2600    
2601            /**
2602             * Replaces the first occurrence of the string with the new string.
2603             *
2604             * @param  s the original string
2605             * @param  oldSub the string whose first occurrence in the original string
2606             *         is to be searched for and replaced
2607             * @param  newSub the string with which to replace the first occurrence of
2608             *         the <code>oldSub</code> string
2609             * @return a string representing the original string except with the first
2610             *         occurrence of the string <code>oldSub</code> replaced with the
2611             *         string <code>newSub</code>
2612             */
2613            public static String replaceFirst(String s, String oldSub, String newSub) {
2614                    return replaceFirst(s, oldSub, newSub, 0);
2615            }
2616    
2617            /**
2618             * Replaces the first occurrences of the elements of the string array with
2619             * the corresponding elements of the new string array, beginning the element
2620             * search from the index position.
2621             *
2622             * @param  s the original string
2623             * @param  oldSub the strings whose first occurrences are to be searched for
2624             *         and replaced in the original string
2625             * @param  newSub the strings with which to replace the first occurrences of
2626             *         the <code>oldSubs</code> strings
2627             * @param  fromIndex the start index within the string
2628             * @return a string representing the original string with the first
2629             *         occurrences of the <code>oldSubs</code> strings replaced with the
2630             *         corresponding <code>newSubs</code> strings, or <code>null</code>
2631             *         if the original string, the <code>oldSubs</code> string, or the
2632             *         <code>newSubs</code> string is <code>null</code>
2633             */
2634            public static String replaceFirst(
2635                    String s, String oldSub, String newSub, int fromIndex) {
2636    
2637                    if ((s == null) || (oldSub == null) || (newSub == null)) {
2638                            return null;
2639                    }
2640    
2641                    if (oldSub.equals(newSub)) {
2642                            return s;
2643                    }
2644    
2645                    int y = s.indexOf(oldSub, fromIndex);
2646    
2647                    if (y >= 0) {
2648                            return s.substring(0, y).concat(newSub).concat(
2649                                    s.substring(y + oldSub.length()));
2650                    }
2651                    else {
2652                            return s;
2653                    }
2654            }
2655    
2656            /**
2657             * Replaces the first occurrences of the elements of the string array with
2658             * the corresponding elements of the new string array.
2659             *
2660             * @param  s the original string
2661             * @param  oldSubs the strings whose first occurrences are to be searched
2662             *         for and replaced in the original string
2663             * @param  newSubs the strings with which to replace the first occurrences
2664             *         of the <code>oldSubs</code> strings
2665             * @return a string representing the original string with the first
2666             *         occurrences of the <code>oldSubs</code> strings replaced with the
2667             *         corresponding <code>newSubs</code> strings, or <code>null</code>
2668             *         if the original string, the <code>oldSubs</code> array, or the
2669             *         <code>newSubs</code> is <code>null</code>
2670             */
2671            public static String replaceFirst(
2672                    String s, String[] oldSubs, String[] newSubs) {
2673    
2674                    if ((s == null) || (oldSubs == null) || (newSubs == null)) {
2675                            return null;
2676                    }
2677    
2678                    if (oldSubs.length != newSubs.length) {
2679                            return s;
2680                    }
2681    
2682                    for (int i = 0; i < oldSubs.length; i++) {
2683                            s = replaceFirst(s, oldSubs[i], newSubs[i]);
2684                    }
2685    
2686                    return s;
2687            }
2688    
2689            /**
2690             * Replaces the last occurrence of the character with the new character.
2691             *
2692             * @param  s the original string
2693             * @param  oldSub the character whose last occurrence in the original string
2694             *         is to be searched for and replaced
2695             * @param  newSub the character with which to replace the last occurrence of
2696             *         the <code>oldSub</code> character
2697             * @return a string representing the original string except with the first
2698             *         occurrence of the character <code>oldSub</code> replaced with the
2699             *         character <code>newSub</code>
2700             */
2701            public static String replaceLast(String s, char oldSub, char newSub) {
2702                    if (s == null) {
2703                            return null;
2704                    }
2705    
2706                    return replaceLast(s, String.valueOf(oldSub), String.valueOf(newSub));
2707            }
2708    
2709            /**
2710             * Replaces the last occurrence of the character with the new string.
2711             *
2712             * @param  s the original string
2713             * @param  oldSub the character whose last occurrence in the original string
2714             *         is to be searched for and replaced
2715             * @param  newSub the string with which to replace the last occurrence of
2716             *         the <code>oldSub</code> character
2717             * @return a string representing the original string except with the last
2718             *         occurrence of the character <code>oldSub</code> replaced with the
2719             *         string <code>newSub</code>
2720             */
2721            public static String replaceLast(String s, char oldSub, String newSub) {
2722                    if ((s == null) || (newSub == null)) {
2723                            return null;
2724                    }
2725    
2726                    return replaceLast(s, String.valueOf(oldSub), newSub);
2727            }
2728    
2729            /**
2730             * Replaces the last occurrence of the string <code>oldSub</code> in the
2731             * string <code>s</code> with the string <code>newSub</code>.
2732             *
2733             * @param  s the original string
2734             * @param  oldSub the string whose last occurrence in the original string is
2735             *         to be searched for and replaced
2736             * @param  newSub the string with which to replace the last occurrence of
2737             *         the <code>oldSub</code> string
2738             * @return a string representing the original string except with the last
2739             *         occurrence of the string <code>oldSub</code> replaced with the
2740             *         string <code>newSub</code>
2741             */
2742            public static String replaceLast(String s, String oldSub, String newSub) {
2743                    if ((s == null) || (oldSub == null) || (newSub == null)) {
2744                            return null;
2745                    }
2746    
2747                    if (oldSub.equals(newSub)) {
2748                            return s;
2749                    }
2750    
2751                    int y = s.lastIndexOf(oldSub);
2752    
2753                    if (y >= 0) {
2754                            return s.substring(0, y).concat(newSub).concat(
2755                                    s.substring(y + oldSub.length()));
2756                    }
2757                    else {
2758                            return s;
2759                    }
2760            }
2761    
2762            /**
2763             * Replaces the last occurrences of the elements of the string array with
2764             * the corresponding elements of the new string array.
2765             *
2766             * @param  s the original string
2767             * @param  oldSubs the strings whose last occurrences are to be searched for
2768             *         and replaced in the original string
2769             * @param  newSubs the strings with which to replace the last occurrences of
2770             *         the <code>oldSubs</code> strings
2771             * @return a string representing the original string with the last
2772             *         occurrences of the <code>oldSubs</code> strings replaced with the
2773             *         corresponding <code>newSubs</code> strings, or <code>null</code>
2774             *         if the original string, the <code>oldSubs</code> array, or the
2775             *         <code>newSubs</code> is <code>null</code>
2776             */
2777            public static String replaceLast(
2778                    String s, String[] oldSubs, String[] newSubs) {
2779    
2780                    if ((s == null) || (oldSubs == null) || (newSubs == null)) {
2781                            return null;
2782                    }
2783    
2784                    if (oldSubs.length != newSubs.length) {
2785                            return s;
2786                    }
2787    
2788                    for (int i = 0; i < oldSubs.length; i++) {
2789                            s = replaceLast(s, oldSubs[i], newSubs[i]);
2790                    }
2791    
2792                    return s;
2793            }
2794    
2795            /**
2796             * Replaces all occurrences of the keywords found in the substring, defined
2797             * by the beginning and ending strings, with the new values. The result is
2798             * returned as a {@link StringBundler}.
2799             *
2800             * <p>
2801             * For example, with the following initialized variables:
2802             * </p>
2803             *
2804             * <p>
2805             * <pre>
2806             * <code>
2807             * String s = "http://www.example-url/${userId}";
2808             * String begin = "${";
2809             * String end = "}";
2810             * Map<String, String> values =  new HashMap&#60;String, String&#62;();
2811             * values.put("userId", "jbloggs");
2812             * </code>
2813             * </pre>
2814             * </p>
2815             *
2816             * <p>
2817             * <code>StringBundler sb = replaceToStringBundler(s, begin, end,
2818             * values)</code> <code>sb.toString()</code> returns
2819             * <code>"http://www.example-url/jbloggs"</code>
2820             * </p>
2821             *
2822             * @param  s the original string
2823             * @param  begin the string preceding the substring to be modified. This
2824             *         string is excluded from the result.
2825             * @param  end the string following the substring to be modified. This
2826             *         string is excluded from the result.
2827             * @param  values the key-value map values
2828             * @return a string bundler representing the original string with all
2829             *         occurrences of the keywords found in the substring, replaced with
2830             *         the new values. <code>null</code> is returned if the original
2831             *         string, the beginning string, the ending string, or the key-map
2832             *         values are <code>null</code>.
2833             * @see    #replace(String, String, String, Map)
2834             */
2835            public static StringBundler replaceToStringBundler(
2836                    String s, String begin, String end, Map<String, String> values) {
2837    
2838                    if (Validator.isBlank(s) || Validator.isBlank(begin) ||
2839                            Validator.isBlank(end) || MapUtil.isEmpty(values)) {
2840    
2841                            return new StringBundler(s);
2842                    }
2843    
2844                    StringBundler sb = new StringBundler(values.size() * 2 + 1);
2845    
2846                    int pos = 0;
2847    
2848                    while (true) {
2849                            int x = s.indexOf(begin, pos);
2850                            int y = s.indexOf(end, x + begin.length());
2851    
2852                            if ((x == -1) || (y == -1)) {
2853                                    sb.append(s.substring(pos));
2854    
2855                                    break;
2856                            }
2857                            else {
2858                                    sb.append(s.substring(pos, x));
2859    
2860                                    String oldValue = s.substring(x + begin.length(), y);
2861    
2862                                    String newValue = values.get(oldValue);
2863    
2864                                    if (newValue == null) {
2865                                            newValue = oldValue;
2866                                    }
2867    
2868                                    sb.append(newValue);
2869    
2870                                    pos = y + end.length();
2871                            }
2872                    }
2873    
2874                    return sb;
2875            }
2876    
2877            /**
2878             * Replaces all occurrences of the keywords found in the substring, defined
2879             * by the beginning and ending strings, with the new values. The result is
2880             * returned as a {@link StringBundler}.
2881             *
2882             * @param  s the original string
2883             * @param  begin the string preceding the substring to be modified. This
2884             *         string is removed from the result.
2885             * @param  end the string following the substring to be modified. This
2886             *         string is removed from the result.
2887             * @param  values the key-value map values, which has string keys and {@link
2888             *         StringBundler} values
2889             * @return a string bundler representing the original string with all
2890             *         occurrences of the keywords found in the substring, replaced with
2891             *         the new values. <code>null</code> is returned if the original
2892             *         string, the beginning string, the ending string, or the key-map
2893             *         values are <code>null</code>.
2894             */
2895            public static StringBundler replaceWithStringBundler(
2896                    String s, String begin, String end, Map<String, StringBundler> values) {
2897    
2898                    if (Validator.isBlank(s) || Validator.isBlank(begin) ||
2899                            Validator.isBlank(end) || MapUtil.isEmpty(values)) {
2900    
2901                            return new StringBundler(s);
2902                    }
2903    
2904                    int size = values.size() + 1;
2905    
2906                    for (StringBundler valueSB : values.values()) {
2907                            size += valueSB.index();
2908                    }
2909    
2910                    StringBundler sb = new StringBundler(size);
2911    
2912                    int pos = 0;
2913    
2914                    while (true) {
2915                            int x = s.indexOf(begin, pos);
2916                            int y = s.indexOf(end, x + begin.length());
2917    
2918                            if ((x == -1) || (y == -1)) {
2919                                    sb.append(s.substring(pos));
2920    
2921                                    break;
2922                            }
2923                            else {
2924                                    sb.append(s.substring(pos, x));
2925    
2926                                    String oldValue = s.substring(x + begin.length(), y);
2927    
2928                                    StringBundler newValue = values.get(oldValue);
2929    
2930                                    if (newValue == null) {
2931                                            sb.append(oldValue);
2932                                    }
2933                                    else {
2934                                            sb.append(newValue);
2935                                    }
2936    
2937                                    pos = y + end.length();
2938                            }
2939                    }
2940    
2941                    return sb;
2942            }
2943    
2944            /**
2945             * Reverses the order of the characters of the string.
2946             *
2947             * @param  s the original string
2948             * @return a string representing the original string with characters in
2949             *         reverse order
2950             */
2951            public static String reverse(String s) {
2952                    if (s == null) {
2953                            return null;
2954                    }
2955    
2956                    char[] chars = s.toCharArray();
2957                    char[] reverse = new char[chars.length];
2958    
2959                    for (int i = 0; i < chars.length; i++) {
2960                            reverse[i] = chars[chars.length - i - 1];
2961                    }
2962    
2963                    return new String(reverse);
2964            }
2965    
2966            /**
2967             * Replaces all double slashes of the string with single slashes.
2968             *
2969             * <p>
2970             * Example:
2971             * </p>
2972             *
2973             * <p>
2974             * <pre>
2975             * <code>
2976             * safePath("http://www.liferay.com") returns "http:/www.liferay.com"
2977             * </code>
2978             * </pre>
2979             * </p>
2980             *
2981             * @param  path the original string
2982             * @return a string representing the original string with all double slashes
2983             *         replaced with single slashes
2984             */
2985            public static String safePath(String path) {
2986                    return replace(path, StringPool.DOUBLE_SLASH, StringPool.SLASH);
2987            }
2988    
2989            /**
2990             * Returns a string representing the original string appended with suffix
2991             * "..." and then shortened to 20 characters.
2992             *
2993             * <p>
2994             * The suffix is only added if the original string exceeds 20 characters. If
2995             * the original string exceeds 20 characters and it contains whitespace, the
2996             * string is shortened at the first whitespace character.
2997             * </p>
2998             *
2999             * <p>
3000             * Examples:
3001             * </p>
3002             *
3003             * <p>
3004             * <pre>
3005             * <code>
3006             * shorten("12345678901234567890xyz") returns "12345678901234567..."
3007             * shorten("1 345678901234567890xyz") returns "1..."
3008             * shorten(" 2345678901234567890xyz") returns "..."
3009             * shorten("12345678901234567890") returns "12345678901234567890"
3010             * shorten(" 2345678901234567890") returns " 2345678901234567890"
3011             * </code>
3012             * </pre>
3013             * </p>
3014             *
3015             * @param  s the original string
3016             * @return a string representing the original string shortened to 20
3017             *         characters, with suffix "..." appended to it
3018             */
3019            public static String shorten(String s) {
3020                    return shorten(s, 20);
3021            }
3022    
3023            /**
3024             * Returns a string representing the original string appended with suffix
3025             * "..." and then shortened to the specified length.
3026             *
3027             * <p>
3028             * The suffix is only added if the original string exceeds the specified
3029             * length. If the original string exceeds the specified length and it
3030             * contains whitespace, the string is shortened at the first whitespace
3031             * character.
3032             * </p>
3033             *
3034             * <p>
3035             * Examples:
3036             * </p>
3037             *
3038             * <p>
3039             * <pre>
3040             * <code>
3041             * shorten("123456789", 8) returns "12345..."
3042             * shorten("1 3456789", 8) returns "1..."
3043             * shorten(" 23456789", 8) returns "..."
3044             * shorten("12345678", 8) returns "12345678"
3045             * shorten(" 1234567", 8) returns " 1234567"
3046             * </code>
3047             * </pre>
3048             * </p>
3049             *
3050             * @param  s the original string
3051             * @param  length the number of characters to limit from the original string
3052             * @return a string representing the original string shortened to the
3053             *         specified length, with suffix "..." appended to it
3054             */
3055            public static String shorten(String s, int length) {
3056                    return shorten(s, length, "...");
3057            }
3058    
3059            /**
3060             * Returns a string representing the original string appended with the
3061             * specified suffix and then shortened to the specified length.
3062             *
3063             * <p>
3064             * The suffix is only added if the original string exceeds the specified
3065             * length. If the original string exceeds the specified length and it
3066             * contains whitespace, the string is shortened at the first whitespace
3067             * character.
3068             * </p>
3069             *
3070             * <p>
3071             * Examples:
3072             * </p>
3073             *
3074             * <p>
3075             * <pre>
3076             * <code>
3077             * shorten("12345678901234", 13, "... etc.") returns "12345... etc."
3078             * shorten("1 345678901234", 13, "... etc.") returns "1... etc."
3079             * shorten(" 2345678901234", 13, "... etc.") returns "... etc."
3080             * shorten("1234567890123", 13, "... etc.") returns "1234567890123"
3081             * shorten(" 123456789012", 13, "... etc.") returns " 123456789012"
3082             * </code>
3083             * </pre>
3084             * </p>
3085             *
3086             * @param  s the original string
3087             * @param  length the number of characters to limit from the original string
3088             * @param  suffix the suffix to append
3089             * @return a string representing the original string shortened to the
3090             *         specified length, with the specified suffix appended to it
3091             */
3092            public static String shorten(String s, int length, String suffix) {
3093                    if ((s == null) || (suffix == null)) {
3094                            return null;
3095                    }
3096    
3097                    if (s.length() <= length) {
3098                            return s;
3099                    }
3100    
3101                    if (length < suffix.length()) {
3102                            return s.substring(0, length);
3103                    }
3104    
3105                    int curLength = length;
3106    
3107                    for (int j = (curLength - suffix.length()); j >= 0; j--) {
3108                            if (Character.isWhitespace(s.charAt(j))) {
3109                                    curLength = j;
3110    
3111                                    break;
3112                            }
3113                    }
3114    
3115                    if (curLength == length) {
3116                            curLength = length - suffix.length();
3117                    }
3118    
3119                    String temp = s.substring(0, curLength);
3120    
3121                    return temp.concat(suffix);
3122            }
3123    
3124            /**
3125             * Returns a string representing the original string appended with the
3126             * specified suffix and then shortened to 20 characters.
3127             *
3128             * <p>
3129             * The suffix is only added if the original string exceeds 20 characters. If
3130             * the original string exceeds 20 characters and it contains whitespace, the
3131             * string is shortened at the first whitespace character.
3132             * </p>
3133             *
3134             * <p>
3135             * Examples:
3136             * </p>
3137             *
3138             * <p>
3139             * <pre>
3140             * <code>
3141             * shorten("12345678901234567890xyz", "... etc.") returns "123456789012... etc."
3142             * shorten("1 345678901234567890xyz", "... etc.") returns "1... etc."
3143             * shorten(" 2345678901234567890xyz", "... etc.") returns "... etc."
3144             * shorten("12345678901234567890", "... etc.") returns "12345678901234567890"
3145             * shorten(" 2345678901234567890", "... etc.") returns " 2345678901234567890"
3146             * </code>
3147             * </pre>
3148             * </p>
3149             *
3150             * @param  s the original string
3151             * @param  suffix the suffix to append
3152             * @return a string representing the original string shortened to 20
3153             *         characters, with the specified suffix appended to it
3154             */
3155            public static String shorten(String s, String suffix) {
3156                    return shorten(s, 20, suffix);
3157            }
3158    
3159            /**
3160             * Splits string <code>s</code> around comma characters.
3161             *
3162             * <p>
3163             * Example:
3164             * </p>
3165             *
3166             * <p>
3167             * <pre>
3168             * <code>
3169             * split("Alice,Bob,Charlie") returns {"Alice", "Bob", "Charlie"}
3170             * split("Alice, Bob, Charlie") returns {"Alice", " Bob", " Charlie"}
3171             * </code>
3172             * </pre>
3173             * </p>
3174             *
3175             * @param  s the string to split
3176             * @return the array of strings resulting from splitting string
3177             *         <code>s</code> around comma characters, or an empty string array
3178             *         if <code>s</code> is <code>null</code> or <code>s</code> is empty
3179             */
3180            public static String[] split(String s) {
3181                    return split(s, CharPool.COMMA);
3182            }
3183    
3184            /**
3185             * Splits the string <code>s</code> around comma characters returning the
3186             * boolean values of the substrings.
3187             *
3188             * @param  s the string to split
3189             * @param  x the default value to use for a substring in case an exception
3190             *         occurs in getting the boolean value for that substring
3191             * @return the array of boolean values resulting from splitting string
3192             *         <code>s</code> around comma characters, or an empty array if
3193             *         <code>s</code> is <code>null</code>
3194             */
3195            public static boolean[] split(String s, boolean x) {
3196                    return split(s, StringPool.COMMA, x);
3197            }
3198    
3199            /**
3200             * Splits the string <code>s</code> around the specified delimiter.
3201             *
3202             * <p>
3203             * Example:
3204             * </p>
3205             *
3206             * <p>
3207             * <pre>
3208             * <code>
3209             * splitLines("First;Second;Third", ';') returns {"First","Second","Third"}
3210             * </code>
3211             * </pre>
3212             * </p>
3213             *
3214             * @param  s the string to split
3215             * @param  delimiter the delimiter
3216             * @return the array of strings resulting from splitting string
3217             *         <code>s</code> around the specified delimiter character, or an
3218             *         empty string array if <code>s</code> is <code>null</code> or if
3219             *         <code>s</code> is empty
3220             */
3221            public static String[] split(String s, char delimiter) {
3222                    if (Validator.isNull(s)) {
3223                            return _emptyStringArray;
3224                    }
3225    
3226                    s = s.trim();
3227    
3228                    if (s.length() == 0) {
3229                            return _emptyStringArray;
3230                    }
3231    
3232                    if ((delimiter == CharPool.RETURN) ||
3233                            (delimiter == CharPool.NEW_LINE)) {
3234    
3235                            return splitLines(s);
3236                    }
3237    
3238                    List<String> nodeValues = new ArrayList<String>();
3239    
3240                    int offset = 0;
3241                    int pos = s.indexOf(delimiter, offset);
3242    
3243                    while (pos != -1) {
3244                            nodeValues.add(s.substring(offset, pos));
3245    
3246                            offset = pos + 1;
3247                            pos = s.indexOf(delimiter, offset);
3248                    }
3249    
3250                    if (offset < s.length()) {
3251                            nodeValues.add(s.substring(offset));
3252                    }
3253    
3254                    return nodeValues.toArray(new String[nodeValues.size()]);
3255            }
3256    
3257            /**
3258             * Splits the string <code>s</code> around comma characters returning the
3259             * double-precision decimal values of the substrings.
3260             *
3261             * @param  s the string to split
3262             * @param  x the default value to use for a substring in case an exception
3263             *         occurs in getting the double-precision decimal value for that
3264             *         substring
3265             * @return the array of double-precision decimal values resulting from
3266             *         splitting string <code>s</code> around comma characters, or an
3267             *         empty array if <code>s</code> is <code>null</code>
3268             */
3269            public static double[] split(String s, double x) {
3270                    return split(s, StringPool.COMMA, x);
3271            }
3272    
3273            /**
3274             * Splits the string <code>s</code> around comma characters returning the
3275             * decimal values of the substrings.
3276             *
3277             * @param  s the string to split
3278             * @param  x the default value to use for a substring in case an exception
3279             *         occurs in getting the decimal value for that substring
3280             * @return the array of decimal values resulting from splitting string
3281             *         <code>s</code> around comma characters, or an empty array if
3282             *         <code>s</code> is <code>null</code>
3283             */
3284            public static float[] split(String s, float x) {
3285                    return split(s, StringPool.COMMA, x);
3286            }
3287    
3288            /**
3289             * Splits the string <code>s</code> around comma characters returning the
3290             * integer values of the substrings.
3291             *
3292             * @param  s the string to split
3293             * @param  x the default value to use for a substring in case an exception
3294             *         occurs in getting the integer value for that substring
3295             * @return the array of integer values resulting from splitting string
3296             *         <code>s</code> around comma characters, or an empty array if
3297             *         <code>s</code> is <code>null</code>
3298             */
3299            public static int[] split(String s, int x) {
3300                    return split(s, StringPool.COMMA, x);
3301            }
3302    
3303            /**
3304             * Splits the string <code>s</code> around comma characters returning the
3305             * long integer values of the substrings.
3306             *
3307             * @param  s the string to split
3308             * @param  x the default value to use for a substring in case an exception
3309             *         occurs in getting the long integer value for that substring
3310             * @return the array of long integer values resulting from splitting string
3311             *         <code>s</code> around comma characters, or an empty array if
3312             *         <code>s</code> is <code>null</code>
3313             */
3314            public static long[] split(String s, long x) {
3315                    return split(s, StringPool.COMMA, x);
3316            }
3317    
3318            /**
3319             * Splits the string <code>s</code> around comma characters returning the
3320             * short integer values of the substrings.
3321             *
3322             * @param  s the string to split
3323             * @param  x the default value to use for a substring in case an exception
3324             *         occurs in getting the short integer value for that substring
3325             * @return the array of short integer values resulting from splitting string
3326             *         <code>s</code> around comma characters, or an empty array if
3327             *         <code>s</code> is <code>null</code>
3328             */
3329            public static short[] split(String s, short x) {
3330                    return split(s, StringPool.COMMA, x);
3331            }
3332    
3333            /**
3334             * Splits the string <code>s</code> around the specified delimiter string.
3335             *
3336             * <p>
3337             * Example:
3338             * </p>
3339             *
3340             * <p>
3341             * <pre>
3342             * <code>
3343             * splitLines("oneandtwoandthreeandfour", "and") returns {"one","two","three","four"}
3344             * </code>
3345             * </pre>
3346             * </p>
3347             *
3348             * @param  s the string to split
3349             * @param  delimiter the delimiter
3350             * @return the array of strings resulting from splitting string
3351             *         <code>s</code> around the specified delimiter string, or an empty
3352             *         string array if <code>s</code> is <code>null</code> or equals the
3353             *         delimiter
3354             */
3355            public static String[] split(String s, String delimiter) {
3356                    if (Validator.isNull(s) || (delimiter == null) ||
3357                            delimiter.equals(StringPool.BLANK)) {
3358    
3359                            return _emptyStringArray;
3360                    }
3361    
3362                    s = s.trim();
3363    
3364                    if (s.equals(delimiter)) {
3365                            return _emptyStringArray;
3366                    }
3367    
3368                    if (delimiter.length() == 1) {
3369                            return split(s, delimiter.charAt(0));
3370                    }
3371    
3372                    List<String> nodeValues = new ArrayList<String>();
3373    
3374                    int offset = 0;
3375                    int pos = s.indexOf(delimiter, offset);
3376    
3377                    while (pos != -1) {
3378                            nodeValues.add(s.substring(offset, pos));
3379    
3380                            offset = pos + delimiter.length();
3381                            pos = s.indexOf(delimiter, offset);
3382                    }
3383    
3384                    if (offset < s.length()) {
3385                            nodeValues.add(s.substring(offset));
3386                    }
3387    
3388                    return nodeValues.toArray(new String[nodeValues.size()]);
3389            }
3390    
3391            /**
3392             * Splits the string <code>s</code> around the specified delimiter returning
3393             * the boolean values of the substrings.
3394             *
3395             * @param  s the string to split
3396             * @param  delimiter the delimiter
3397             * @param  x the default value to use for a substring in case an exception
3398             *         occurs in getting the boolean value for that substring
3399             * @return the array of booleans resulting from splitting string
3400             *         <code>s</code> around the specified delimiter string, or an empty
3401             *         array if <code>s</code> is <code>null</code>
3402             */
3403            public static boolean[] split(String s, String delimiter, boolean x) {
3404                    String[] array = split(s, delimiter);
3405                    boolean[] newArray = new boolean[array.length];
3406    
3407                    for (int i = 0; i < array.length; i++) {
3408                            boolean value = x;
3409    
3410                            try {
3411                                    value = Boolean.valueOf(array[i]).booleanValue();
3412                            }
3413                            catch (Exception e) {
3414                            }
3415    
3416                            newArray[i] = value;
3417                    }
3418    
3419                    return newArray;
3420            }
3421    
3422            /**
3423             * Splits the string <code>s</code> around the specified delimiter returning
3424             * the double-precision decimal values of the substrings.
3425             *
3426             * @param  s the string to split
3427             * @param  delimiter the delimiter
3428             * @param  x the default value to use for a substring in case an exception
3429             *         occurs in getting the double-precision decimal value for that
3430             *         substring
3431             * @return the array of double-precision decimal values resulting from
3432             *         splitting string <code>s</code> around the specified delimiter
3433             *         string, or an empty array if <code>s</code> is <code>null</code>
3434             */
3435            public static double[] split(String s, String delimiter, double x) {
3436                    String[] array = split(s, delimiter);
3437                    double[] newArray = new double[array.length];
3438    
3439                    for (int i = 0; i < array.length; i++) {
3440                            double value = x;
3441    
3442                            try {
3443                                    value = Double.parseDouble(array[i]);
3444                            }
3445                            catch (Exception e) {
3446                            }
3447    
3448                            newArray[i] = value;
3449                    }
3450    
3451                    return newArray;
3452            }
3453    
3454            /**
3455             * Splits the string <code>s</code> around the specified delimiter returning
3456             * the decimal values of the substrings.
3457             *
3458             * @param  s the string to split
3459             * @param  delimiter the delimiter
3460             * @param  x the default value to use for a substring in case an exception
3461             *         occurs in getting the decimal value for that substring
3462             * @return the array of decimal values resulting from splitting string
3463             *         <code>s</code> around the specified delimiter string, or an empty
3464             *         array if <code>s</code> is <code>null</code>
3465             */
3466            public static float[] split(String s, String delimiter, float x) {
3467                    String[] array = split(s, delimiter);
3468                    float[] newArray = new float[array.length];
3469    
3470                    for (int i = 0; i < array.length; i++) {
3471                            float value = x;
3472    
3473                            try {
3474                                    value = Float.parseFloat(array[i]);
3475                            }
3476                            catch (Exception e) {
3477                            }
3478    
3479                            newArray[i] = value;
3480                    }
3481    
3482                    return newArray;
3483            }
3484    
3485            /**
3486             * Splits the string <code>s</code> around the specified delimiter returning
3487             * the integer values of the substrings.
3488             *
3489             * @param  s the string to split
3490             * @param  delimiter the delimiter
3491             * @param  x the default value to use for a substring in case an exception
3492             *         occurs in getting the integer value for that substring
3493             * @return the array of integer values resulting from splitting string
3494             *         <code>s</code> around the specified delimiter string, or an empty
3495             *         array if <code>s</code> is <code>null</code>
3496             */
3497            public static int[] split(String s, String delimiter, int x) {
3498                    String[] array = split(s, delimiter);
3499                    int[] newArray = new int[array.length];
3500    
3501                    for (int i = 0; i < array.length; i++) {
3502                            int value = x;
3503    
3504                            try {
3505                                    value = Integer.parseInt(array[i]);
3506                            }
3507                            catch (Exception e) {
3508                            }
3509    
3510                            newArray[i] = value;
3511                    }
3512    
3513                    return newArray;
3514            }
3515    
3516            /**
3517             * Splits the string <code>s</code> around the specified delimiter returning
3518             * the long integer values of the substrings.
3519             *
3520             * @param  s the string to split
3521             * @param  delimiter the delimiter
3522             * @param  x the default value to use for a substring in case an exception
3523             *         occurs in getting the long integer value for that substring
3524             * @return the array of long integer values resulting from splitting string
3525             *         <code>s</code> around the specified delimiter string, or an empty
3526             *         array if <code>s</code> is <code>null</code>
3527             */
3528            public static long[] split(String s, String delimiter, long x) {
3529                    String[] array = split(s, delimiter);
3530                    long[] newArray = new long[array.length];
3531    
3532                    for (int i = 0; i < array.length; i++) {
3533                            long value = x;
3534    
3535                            try {
3536                                    value = Long.parseLong(array[i]);
3537                            }
3538                            catch (Exception e) {
3539                            }
3540    
3541                            newArray[i] = value;
3542                    }
3543    
3544                    return newArray;
3545            }
3546    
3547            /**
3548             * Splits the string <code>s</code> around the specified delimiter returning
3549             * the short integer values of the substrings.
3550             *
3551             * @param  s the string to split
3552             * @param  delimiter the delimiter
3553             * @param  x the default value to use for a substring in case an exception
3554             *         occurs in getting the short integer value for that substring
3555             * @return the array of short integer values resulting from splitting string
3556             *         <code>s</code> around the specified delimiter string, or an empty
3557             *         array if <code>s</code> is <code>null</code>
3558             */
3559            public static short[] split(String s, String delimiter, short x) {
3560                    String[] array = split(s, delimiter);
3561                    short[] newArray = new short[array.length];
3562    
3563                    for (int i = 0; i < array.length; i++) {
3564                            short value = x;
3565    
3566                            try {
3567                                    value = Short.parseShort(array[i]);
3568                            }
3569                            catch (Exception e) {
3570                            }
3571    
3572                            newArray[i] = value;
3573                    }
3574    
3575                    return newArray;
3576            }
3577    
3578            /**
3579             * Splits string <code>s</code> around return and newline characters.
3580             *
3581             * <p>
3582             * Example:
3583             * </p>
3584             *
3585             * <p>
3586             * <pre>
3587             * <code>
3588             * splitLines("Red\rBlue\nGreen") returns {"Red","Blue","Green"}
3589             * </code>
3590             * </pre>
3591             * </p>
3592             *
3593             * @param  s the string to split
3594             * @return the array of strings resulting from splitting string
3595             *         <code>s</code> around return and newline characters, or an empty
3596             *         string array if string <code>s</code> is <code>null</code>
3597             */
3598            public static String[] splitLines(String s) {
3599                    if (Validator.isNull(s)) {
3600                            return _emptyStringArray;
3601                    }
3602    
3603                    s = s.trim();
3604    
3605                    List<String> lines = new ArrayList<String>();
3606    
3607                    int lastIndex = 0;
3608    
3609                    while (true) {
3610                            int returnIndex = s.indexOf(CharPool.RETURN, lastIndex);
3611                            int newLineIndex = s.indexOf(CharPool.NEW_LINE, lastIndex);
3612    
3613                            if ((returnIndex == -1) && (newLineIndex == -1)) {
3614                                    break;
3615                            }
3616    
3617                            if (returnIndex == -1) {
3618                                    lines.add(s.substring(lastIndex, newLineIndex));
3619    
3620                                    lastIndex = newLineIndex + 1;
3621                            }
3622                            else if (newLineIndex == -1) {
3623                                    lines.add(s.substring(lastIndex, returnIndex));
3624    
3625                                    lastIndex = returnIndex + 1;
3626                            }
3627                            else if (newLineIndex < returnIndex) {
3628                                    lines.add(s.substring(lastIndex, newLineIndex));
3629    
3630                                    lastIndex = newLineIndex + 1;
3631                            }
3632                            else {
3633                                    lines.add(s.substring(lastIndex, returnIndex));
3634    
3635                                    lastIndex = returnIndex + 1;
3636    
3637                                    if (lastIndex == newLineIndex) {
3638                                            lastIndex++;
3639                                    }
3640                            }
3641                    }
3642    
3643                    if (lastIndex < s.length()) {
3644                            lines.add(s.substring(lastIndex));
3645                    }
3646    
3647                    return lines.toArray(new String[lines.size()]);
3648            }
3649    
3650            /**
3651             * Returns <code>true</code> if, ignoring case, the string starts with the
3652             * specified character.
3653             *
3654             * @param  s the string
3655             * @param  begin the character against which the initial character of the
3656             *         string is to be compared
3657             * @return <code>true</code> if, ignoring case, the string starts with the
3658             *         specified character; <code>false</code> otherwise
3659             */
3660            public static boolean startsWith(String s, char begin) {
3661                    return startsWith(s, (new Character(begin)).toString());
3662            }
3663    
3664            /**
3665             * Returns <code>true</code> if, ignoring case, the string starts with the
3666             * specified start string.
3667             *
3668             * @param  s the original string
3669             * @param  start the string against which the beginning of string
3670             *         <code>s</code> are to be compared
3671             * @return <code>true</code> if, ignoring case, the string starts with the
3672             *         specified start string; <code>false</code> otherwise
3673             */
3674            public static boolean startsWith(String s, String start) {
3675                    if ((s == null) || (start == null)) {
3676                            return false;
3677                    }
3678    
3679                    if (start.length() > s.length()) {
3680                            return false;
3681                    }
3682    
3683                    String temp = s.substring(0, start.length());
3684    
3685                    if (equalsIgnoreCase(temp, start)) {
3686                            return true;
3687                    }
3688                    else {
3689                            return false;
3690                    }
3691            }
3692    
3693            /**
3694             * Returns the number of starting characters that <code>s1</code> and
3695             * <code>s2</code> have in common before their characters deviate.
3696             *
3697             * @param  s1 string 1
3698             * @param  s2 string 2
3699             * @return the number of starting characters that <code>s1</code> and
3700             *         <code>s2</code> have in common before their characters deviate
3701             */
3702            public static int startsWithWeight(String s1, String s2) {
3703                    if ((s1 == null) || (s2 == null)) {
3704                            return 0;
3705                    }
3706    
3707                    char[] chars1 = s1.toCharArray();
3708                    char[] chars2 = s2.toCharArray();
3709    
3710                    int i = 0;
3711    
3712                    for (; (i < chars1.length) && (i < chars2.length); i++) {
3713                            if (chars1[i] != chars2[i]) {
3714                                    break;
3715                            }
3716                    }
3717    
3718                    return i;
3719            }
3720    
3721            /**
3722             * Returns a string representing the string <code>s</code> with all
3723             * occurrences of the specified character removed.
3724             *
3725             * <p>
3726             * Example:
3727             * </p>
3728             *
3729             * <p>
3730             * <pre>
3731             * <code>
3732             * strip("Mississipi", 'i') returns "Mssssp"
3733             * </code>
3734             * </pre>
3735             * </p>
3736             *
3737             * @param  s the string from which to strip all occurrences of the character
3738             * @param  remove the character to strip from the string
3739             * @return a string representing the string <code>s</code> with all
3740             *         occurrences of the specified character removed, or
3741             *         <code>null</code> if <code>s</code> is <code>null</code>
3742             */
3743            public static String strip(String s, char remove) {
3744                    if (s == null) {
3745                            return null;
3746                    }
3747    
3748                    int x = s.indexOf(remove);
3749    
3750                    if (x < 0) {
3751                            return s;
3752                    }
3753    
3754                    int y = 0;
3755    
3756                    StringBundler sb = new StringBundler(s.length());
3757    
3758                    while (x >= 0) {
3759                            sb.append(s.subSequence(y, x));
3760    
3761                            y = x + 1;
3762    
3763                            x = s.indexOf(remove, y);
3764                    }
3765    
3766                    sb.append(s.substring(y));
3767    
3768                    return sb.toString();
3769            }
3770    
3771            /**
3772             * Returns a string representing the combination of the substring of
3773             * <code>s</code> up to but not including the string <code>begin</code>
3774             * concatenated with the substring of <code>s</code> after but not including
3775             * the string <code>end</code>.
3776             *
3777             * <p>
3778             * Example:
3779             * <p>
3780             *
3781             * <p>
3782             * <pre>
3783             * <code>
3784             * stripBetween("One small step for man, one giant leap for mankind", "step", "giant ") returns "One small leap for mankind"
3785             * </code>
3786             * </pre>
3787             * </p>
3788             *
3789             * @param  s the string from which to strip a substring
3790             * @param  begin the beginning characters of the substring to be removed
3791             * @param  end the ending characters of the substring to be removed
3792             * @return a string representing the combination of the substring of
3793             *         <code>s</code> up to but not including the string
3794             *         <code>begin</code> concatenated with the substring of
3795             *         <code>s</code> after but not including the string
3796             *         <code>end</code>, or the original string if the value of
3797             *         <code>s</code>, <code>begin</code>, or <code>end</code> are
3798             *         <code>null</code>
3799             */
3800            public static String stripBetween(String s, String begin, String end) {
3801                    if (Validator.isBlank(s) || Validator.isBlank(begin) ||
3802                            Validator.isBlank(end)) {
3803    
3804                            return s;
3805                    }
3806    
3807                    StringBundler sb = new StringBundler(s.length());
3808    
3809                    int pos = 0;
3810    
3811                    while (true) {
3812                            int x = s.indexOf(begin, pos);
3813                            int y = s.indexOf(end, x + begin.length());
3814    
3815                            if ((x == -1) || (y == -1)) {
3816                                    sb.append(s.substring(pos));
3817    
3818                                    break;
3819                            }
3820                            else {
3821                                    sb.append(s.substring(pos, x));
3822    
3823                                    pos = y + end.length();
3824                            }
3825                    }
3826    
3827                    return sb.toString();
3828            }
3829    
3830            /**
3831             * Returns a string representing the string <code>s</code> with its
3832             * <code>&lt;![CDATA[]]&gt;</code> wrapper removed.
3833             *
3834             * <p>
3835             * Example:
3836             * <p>
3837             *
3838             * <p>
3839             * <pre>
3840             * <code>
3841             * stripCDATA("&lt;![CDATA[One small step for man]]&gt;") returns "One small step for man"
3842             * </code>
3843             * </pre>
3844             * </p>
3845             *
3846             * @param  s the string from which to strip its CDATA wrapper
3847             * @return a string representing the string <code>s</code> with its
3848             *         <code>&lt;![CDATA[]]&gt;</code> wrapper removed, or
3849             *         <code>null</code> if <code>s</code> is <code>null</code>
3850             */
3851            public static String stripCDATA(String s) {
3852                    if (s == null) {
3853                            return s;
3854                    }
3855    
3856                    if (s.startsWith(StringPool.CDATA_OPEN) &&
3857                            s.endsWith(StringPool.CDATA_CLOSE)) {
3858    
3859                            s = s.substring(
3860                                    StringPool.CDATA_OPEN.length(),
3861                                    s.length() - StringPool.CDATA_CLOSE.length());
3862                    }
3863    
3864                    return s;
3865            }
3866    
3867            /**
3868             * Returns a string representing the Unicode character codes of the
3869             * characters comprising the string <code>s</code>.
3870             *
3871             * <p>
3872             * Example:
3873             * </p>
3874             *
3875             * <p>
3876             * <pre>
3877             * <code>
3878             * toCharCode("a") returns "97"
3879             * toCharCode("b") returns "98"
3880             * toCharCode("c") returns "99"
3881             * toCharCode("What's for lunch?") returns "87104971163911532102111114321081171109910463"
3882             * </code>
3883             * </pre>
3884             * </p>
3885             *
3886             * @param  s the string whose character codes are to be represented
3887             * @return a string representing the Unicode character codes of the
3888             *         characters comprising the string <code>s</code>
3889             */
3890            public static String toCharCode(String s) {
3891                    StringBundler sb = new StringBundler(s.length());
3892    
3893                    for (int i = 0; i < s.length(); i++) {
3894                            sb.append(s.codePointAt(i));
3895                    }
3896    
3897                    return sb.toString();
3898            }
3899    
3900            /**
3901             * Returns a string representing the hexidecimal character code of the
3902             * integer.
3903             *
3904             * <p>
3905             * Example:
3906             * </p>
3907             *
3908             * <p>
3909             * <pre>
3910             * <code>
3911             * toHexString(10) returns "a"
3912             * toHexString(15) returns "f"
3913             * toHexString(10995) returns "2af3"
3914             * </code>
3915             * </pre>
3916             * </p>
3917             *
3918             * @param  i the integer to convert
3919             * @return a string representing the hexidecimal character code of the
3920             *         integer
3921             */
3922            public static String toHexString(int i) {
3923                    char[] buffer = new char[8];
3924    
3925                    int index = 8;
3926    
3927                    do {
3928                            buffer[--index] = _HEX_DIGITS[i & 15];
3929    
3930                            i >>>= 4;
3931                    }
3932                    while (i != 0);
3933    
3934                    return new String(buffer, index, 8 - index);
3935            }
3936    
3937            /**
3938             * Returns a string representing the hexidecimal character code of the long
3939             * integer.
3940             *
3941             * <p>
3942             * Example:
3943             * </p>
3944             *
3945             * <p>
3946             * <pre>
3947             * <code>
3948             * toHexString(12345678910L) returns "2dfdc1c3e"
3949             * </code>
3950             * </pre>
3951             * </p>
3952             *
3953             * @param  l the long integer to convert
3954             * @return a string representing the hexidecimal character code of the long
3955             *         integer
3956             */
3957            public static String toHexString(long l) {
3958                    char[] buffer = new char[16];
3959    
3960                    int index = 16;
3961    
3962                    do {
3963                            buffer[--index] = _HEX_DIGITS[(int) (l & 15)];
3964    
3965                            l >>>= 4;
3966                    }
3967                    while (l != 0);
3968    
3969                    return new String(buffer, index, 16 - index);
3970            }
3971    
3972            /**
3973             * Returns a string representing the hexidecimal character code of the
3974             * <code>Integer</code> or <code>Long</code> object type. If the object is
3975             * not an instance of these types, the object's original value is returned.
3976             *
3977             * @param  obj the object to convert
3978             * @return a string representing the hexidecimal character code of the
3979             *         object
3980             */
3981            public static String toHexString(Object obj) {
3982                    if (obj instanceof Integer) {
3983                            return toHexString(((Integer)obj).intValue());
3984                    }
3985                    else if (obj instanceof Long) {
3986                            return toHexString(((Long)obj).longValue());
3987                    }
3988                    else {
3989                            return String.valueOf(obj);
3990                    }
3991            }
3992    
3993            /**
3994             * Converts all of the characters in the string to lower case, based on the
3995             * portal instance's default locale.
3996             *
3997             * @param  s the string to convert
3998             * @return the string, converted to lower case, or <code>null</code> if the
3999             *         string is <code>null</code>
4000             */
4001            public static String toLowerCase(String s) {
4002                    return toLowerCase(s, null);
4003            }
4004    
4005            /**
4006             * Converts all of the characters in the string to lower case, based on the
4007             * locale.
4008             *
4009             * @param  s the string to convert
4010             * @param  locale apply this locale's rules
4011             * @return the string, converted to lower case, or <code>null</code> if the
4012             *         string is <code>null</code>
4013             */
4014            public static String toLowerCase(String s, Locale locale) {
4015                    if (s == null) {
4016                            return null;
4017                    }
4018    
4019                    StringBuilder sb = null;
4020    
4021                    for (int i = 0; i < s.length(); i++) {
4022                            char c = s.charAt(i);
4023    
4024                            if (c > 127) {
4025    
4026                                    // Found non-ascii char, fallback to the slow unicode detection
4027    
4028                                    if (locale == null) {
4029                                            locale = LocaleUtil.getDefault();
4030                                    }
4031    
4032                                    return s.toLowerCase(locale);
4033                            }
4034    
4035                            if ((c >= 'A') && (c <= 'Z')) {
4036                                    if (sb == null) {
4037                                            sb = new StringBuilder(s);
4038                                    }
4039    
4040                                    sb.setCharAt(i, (char)(c + 32));
4041                            }
4042                    }
4043    
4044                    if (sb == null) {
4045                            return s;
4046                    }
4047    
4048                    return sb.toString();
4049            }
4050    
4051            /**
4052             * Converts all of the characters in the string to upper case, based on the
4053             * portal instance's default locale.
4054             *
4055             * @param  s the string to convert
4056             * @return the string, converted to upper case, or <code>null</code> if the
4057             *         string is <code>null</code>
4058             */
4059            public static String toUpperCase(String s) {
4060                    return toUpperCase(s, null);
4061            }
4062    
4063            /**
4064             * Converts all of the characters in the string to upper case, based on the
4065             * locale.
4066             *
4067             * @param  s the string to convert
4068             * @param  locale apply this locale's rules
4069             * @return the string, converted to upper case, or <code>null</code> if the
4070             *         string is <code>null</code>
4071             */
4072            public static String toUpperCase(String s, Locale locale) {
4073                    if (s == null) {
4074                            return null;
4075                    }
4076    
4077                    StringBuilder sb = null;
4078    
4079                    for (int i = 0; i < s.length(); i++) {
4080                            char c = s.charAt(i);
4081    
4082                            if (c > 127) {
4083    
4084                                    // Found non-ascii char, fallback to the slow unicode detection
4085    
4086                                    if (locale == null) {
4087                                            locale = LocaleUtil.getDefault();
4088                                    }
4089    
4090                                    return s.toUpperCase(locale);
4091                            }
4092    
4093                            if ((c >= 'a') && (c <= 'z')) {
4094                                    if (sb == null) {
4095                                            sb = new StringBuilder(s);
4096                                    }
4097    
4098                                    sb.setCharAt(i, (char)(c - 32));
4099                            }
4100                    }
4101    
4102                    if (sb == null) {
4103                            return s;
4104                    }
4105    
4106                    return sb.toString();
4107            }
4108    
4109            /**
4110             * Trims all leading and trailing whitespace from the string.
4111             *
4112             * @param  s the original string
4113             * @return a string representing the original string with all leading and
4114             *         trailing whitespace removed
4115             */
4116            public static String trim(String s) {
4117                    if (s == null) {
4118                            return null;
4119                    }
4120    
4121                    if (s.length() == 0) {
4122                            return s;
4123                    }
4124    
4125                    int len = s.length();
4126    
4127                    int x = len;
4128    
4129                    for (int i = 0; i < len; i++) {
4130                            char c = s.charAt(i);
4131    
4132                            if (!Character.isWhitespace(c)) {
4133                                    x = i;
4134    
4135                                    break;
4136                            }
4137                    }
4138    
4139                    if (x == len) {
4140                            return StringPool.BLANK;
4141                    }
4142    
4143                    int y = x + 1;
4144    
4145                    for (int i = len - 1; i > x; i--) {
4146                            char c = s.charAt(i);
4147    
4148                            if (!Character.isWhitespace(c)) {
4149                                    y = i + 1;
4150    
4151                                    break;
4152                            }
4153                    }
4154    
4155                    if ((x == 0) && (y == len)) {
4156                            return s;
4157                    }
4158    
4159                    return s.substring(x, y);
4160            }
4161    
4162            /**
4163             * Trims leading and trailing whitespace from the string, up to but not
4164             * including the whitespace character specified by <code>c</code>.
4165             *
4166             * <p>
4167             * Examples:
4168             * </p>
4169             *
4170             * <p>
4171             * <pre>
4172             * <code>
4173             * trim(" \tHey\t ", '\t') returns "\tHey\t"
4174             * trim(" \t Hey \t ", '\t') returns "\t Hey \t"
4175             * </code>
4176             * </pre>
4177             * </p>
4178             *
4179             * @param  s the original string
4180             * @param  c the whitespace character to limit trimming
4181             * @return a string representing the original string with leading and
4182             *         trailing whitespace removed, up to but not including the
4183             *         whitespace character specified by <code>c</code>
4184             */
4185            public static String trim(String s, char c) {
4186                    return trim(s, new char[] {c});
4187            }
4188    
4189            /**
4190             * Trims leading and trailing whitespace from the string, up to but not
4191             * including the whitespace characters specified by <code>exceptions</code>.
4192             *
4193             * @param  s the original string
4194             * @param  exceptions the whitespace characters to limit trimming
4195             * @return a string representing the original string with leading and
4196             *         trailing whitespace removed, up to but not including the
4197             *         whitespace characters specified by <code>exceptions</code>
4198             */
4199            public static String trim(String s, char[] exceptions) {
4200                    if (s == null) {
4201                            return null;
4202                    }
4203    
4204                    if (s.length() == 0) {
4205                            return s;
4206                    }
4207    
4208                    if (ArrayUtil.isEmpty(exceptions)) {
4209                            return trim(s);
4210                    }
4211    
4212                    int len = s.length();
4213                    int x = len;
4214    
4215                    for (int i = 0; i < len; i++) {
4216                            char c = s.charAt(i);
4217    
4218                            if (!_isTrimable(c, exceptions)) {
4219                                    x = i;
4220    
4221                                    break;
4222                            }
4223                    }
4224    
4225                    if (x == len) {
4226                            return StringPool.BLANK;
4227                    }
4228    
4229                    int y = x + 1;
4230    
4231                    for (int i = len - 1; i > x; i--) {
4232                            char c = s.charAt(i);
4233    
4234                            if (!_isTrimable(c, exceptions)) {
4235                                    y = i + 1;
4236    
4237                                    break;
4238                            }
4239                    }
4240    
4241                    if ((x == 0) && (y == len)) {
4242                            return s;
4243                    }
4244                    else {
4245                            return s.substring(x, y);
4246                    }
4247            }
4248    
4249            /**
4250             * Trims all leading whitespace from the string.
4251             *
4252             * @param  s the original string
4253             * @return a string representing the original string with all leading
4254             *         whitespace removed
4255             */
4256            public static String trimLeading(String s) {
4257                    if (s == null) {
4258                            return null;
4259                    }
4260    
4261                    if (s.length() == 0) {
4262                            return s;
4263                    }
4264    
4265                    int len = s.length();
4266                    int x = len;
4267    
4268                    for (int i = 0; i < len; i++) {
4269                            char c = s.charAt(i);
4270    
4271                            if (!Character.isWhitespace(c)) {
4272                                    x = i;
4273    
4274                                    break;
4275                            }
4276                    }
4277    
4278                    if (x == len) {
4279                            return StringPool.BLANK;
4280                    }
4281                    else if (x == 0) {
4282                            return s;
4283                    }
4284                    else {
4285                            return s.substring(x);
4286                    }
4287            }
4288    
4289            /**
4290             * Trims leading whitespace from the string, up to but not including the
4291             * whitespace character specified by <code>c</code>.
4292             *
4293             * @param  s the original string
4294             * @param  c the whitespace character to limit trimming
4295             * @return a string representing the original string with leading whitespace
4296             *         removed, up to but not including the whitespace character
4297             *         specified by <code>c</code>
4298             */
4299            public static String trimLeading(String s, char c) {
4300                    return trimLeading(s, new char[] {c});
4301            }
4302    
4303            /**
4304             * Trims leading whitespace from the string, up to but not including the
4305             * whitespace characters specified by <code>exceptions</code>.
4306             *
4307             * @param  s the original string
4308             * @param  exceptions the whitespace characters to limit trimming
4309             * @return a string representing the original string with leading whitespace
4310             *         removed, up to but not including the whitespace characters
4311             *         specified by <code>exceptions</code>
4312             */
4313            public static String trimLeading(String s, char[] exceptions) {
4314                    if (s == null) {
4315                            return null;
4316                    }
4317    
4318                    if (s.length() == 0) {
4319                            return s;
4320                    }
4321    
4322                    if (ArrayUtil.isEmpty(exceptions)) {
4323                            return trimLeading(s);
4324                    }
4325    
4326                    int len = s.length();
4327                    int x = len;
4328    
4329                    for (int i = 0; i < len; i++) {
4330                            char c = s.charAt(i);
4331    
4332                            if (!_isTrimable(c, exceptions)) {
4333                                    x = i;
4334    
4335                                    break;
4336                            }
4337                    }
4338    
4339                    if (x == len) {
4340                            return StringPool.BLANK;
4341                    }
4342                    else if (x == 0) {
4343                            return s;
4344                    }
4345                    else {
4346                            return s.substring(x);
4347                    }
4348            }
4349    
4350            /**
4351             * Trims all trailing whitespace from the string.
4352             *
4353             * @param  s the original string
4354             * @return a string representing the original string with all trailing
4355             *         whitespace removed
4356             */
4357            public static String trimTrailing(String s) {
4358                    if (s == null) {
4359                            return null;
4360                    }
4361    
4362                    if (s.length() == 0) {
4363                            return s;
4364                    }
4365    
4366                    int len = s.length();
4367                    int x = 0;
4368    
4369                    for (int i = len - 1; i >= 0; i--) {
4370                            char c = s.charAt(i);
4371    
4372                            if (!Character.isWhitespace(c)) {
4373                                    x = i + 1;
4374    
4375                                    break;
4376                            }
4377                    }
4378    
4379                    if (x == 0) {
4380                            return StringPool.BLANK;
4381                    }
4382                    else if (x == len) {
4383                            return s;
4384                    }
4385                    else {
4386                            return s.substring(0, x);
4387                    }
4388            }
4389    
4390            /**
4391             * Trims trailing whitespace from the string, up to but not including the
4392             * whitespace character specified by <code>c</code>.
4393             *
4394             * @param  s the original string
4395             * @param  c the whitespace character to limit trimming
4396             * @return a string representing the original string with trailing
4397             *         whitespace removed, up to but not including the whitespace
4398             *         character specified by <code>c</code>
4399             */
4400            public static String trimTrailing(String s, char c) {
4401                    return trimTrailing(s, new char[] {c});
4402            }
4403    
4404            /**
4405             * Trims trailing whitespace from the string, up to but not including the
4406             * whitespace characters specified by <code>exceptions</code>.
4407             *
4408             * @param  s the original string
4409             * @param  exceptions the whitespace characters to limit trimming
4410             * @return a string representing the original string with trailing
4411             *         whitespace removed, up to but not including the whitespace
4412             *         characters specified by <code>exceptions</code>
4413             */
4414            public static String trimTrailing(String s, char[] exceptions) {
4415                    if (s == null) {
4416                            return null;
4417                    }
4418    
4419                    if (s.length() == 0) {
4420                            return s;
4421                    }
4422    
4423                    if (ArrayUtil.isEmpty(exceptions)) {
4424                            return trimTrailing(s);
4425                    }
4426    
4427                    int len = s.length();
4428                    int x = 0;
4429    
4430                    for (int i = len - 1; i >= 0; i--) {
4431                            char c = s.charAt(i);
4432    
4433                            if (!_isTrimable(c, exceptions)) {
4434                                    x = i + 1;
4435    
4436                                    break;
4437                            }
4438                    }
4439    
4440                    if (x == 0) {
4441                            return StringPool.BLANK;
4442                    }
4443                    else if (x == len) {
4444                            return s;
4445                    }
4446                    else {
4447                            return s.substring(0, x);
4448                    }
4449            }
4450    
4451            /**
4452             * Removes leading and trailing double and single quotation marks from the
4453             * string.
4454             *
4455             * @param  s the original string
4456             * @return a string representing the original string with leading and
4457             *         trailing double and single quotation marks removed, or the
4458             *         original string if the original string is a <code>null</code> or
4459             *         empty
4460             */
4461            public static String unquote(String s) {
4462                    if (Validator.isNull(s)) {
4463                            return s;
4464                    }
4465    
4466                    if ((s.charAt(0) == CharPool.APOSTROPHE) &&
4467                            (s.charAt(s.length() - 1) == CharPool.APOSTROPHE)) {
4468    
4469                            return s.substring(1, s.length() - 1);
4470                    }
4471                    else if ((s.charAt(0) == CharPool.QUOTE) &&
4472                                     (s.charAt(s.length() - 1) == CharPool.QUOTE)) {
4473    
4474                            return s.substring(1, s.length() - 1);
4475                    }
4476    
4477                    return s;
4478            }
4479    
4480            /**
4481             * Converts all of the characters in the string to upper case.
4482             *
4483             * @param  s the string to convert
4484             * @return the string, converted to upper-case, or <code>null</code> if the
4485             *         string is <code>null</code>
4486             * @see    String#toUpperCase()
4487             */
4488            public static String upperCase(String s) {
4489                    return toUpperCase(s);
4490            }
4491    
4492            /**
4493             * Converts the first character of the string to upper case.
4494             *
4495             * @param  s the string whose first character is to be converted
4496             * @return the string, with its first character converted to upper-case
4497             */
4498            public static String upperCaseFirstLetter(String s) {
4499                    char[] chars = s.toCharArray();
4500    
4501                    if ((chars[0] >= 97) && (chars[0] <= 122)) {
4502                            chars[0] = (char)(chars[0] - 32);
4503                    }
4504    
4505                    return new String(chars);
4506            }
4507    
4508            /**
4509             * Returns the string value of the object.
4510             *
4511             * @param  obj the object whose string value is to be returned
4512             * @return the string value of the object
4513             * @see    String#valueOf(Object obj)
4514             */
4515            public static String valueOf(Object obj) {
4516                    return String.valueOf(obj);
4517            }
4518    
4519            /**
4520             * Returns <code>true</code> if the string matches the wildcard pattern.
4521             *
4522             * <p>
4523             * For example, with the following initialized variables:
4524             * </p>
4525             *
4526             * <p>
4527             * <pre>
4528             * <code>
4529             * String s = "*master";
4530             * String wildcard = "/*m?st*";
4531             * char singleWildcardCharacter = '?';
4532             * char multipleWildcardCharacter = '*';
4533             * char escapeWildcardCharacter = '/';
4534             * boolean caseSensitive = false;
4535             * </code>
4536             * </pre>
4537             * </p>
4538             *
4539             * <p>
4540             * <code>wildcardMatches(s, wildcard, singleWildcardCharacter,
4541             * multipleWildcardCharacter, escapeWildcardCharacter, caseSensitive)</code>
4542             * returns <code>true</code>
4543             * </p>
4544             *
4545             * @param  s the string to be checked
4546             * @param  wildcard the wildcard pattern to match
4547             * @param  singleWildcardCharacter the char used to match exactly one
4548             *         character
4549             * @param  multipleWildcardCharacter the char used to match <code>0</code>
4550             *         or more characters
4551             * @param  escapeWildcardCharacter the char placed in front of a wildcard
4552             *         character to indicate that it should be interpreted as a regular
4553             *         character
4554             * @param  caseSensitive whether to use case sensitivity
4555             * @return <code>true</code> if the string matches the wildcard pattern;
4556             *         <code>false</code> otherwise
4557             */
4558            public static boolean wildcardMatches(
4559                    String s, String wildcard, char singleWildcardCharacter,
4560                    char multipleWildcardCharacter, char escapeWildcardCharacter,
4561                    boolean caseSensitive) {
4562    
4563                    if (!caseSensitive) {
4564                            s = toLowerCase(s);
4565                            wildcard = toLowerCase(wildcard);
4566                    }
4567    
4568                    // Update the wildcard, single whildcard character, and multiple
4569                    // wildcard character so that they no longer have escaped wildcard
4570                    // characters
4571    
4572                    int index = wildcard.indexOf(escapeWildcardCharacter);
4573    
4574                    if (index != -1) {
4575    
4576                            // Search for safe wildcard replacement
4577    
4578                            char newSingleWildcardCharacter = 0;
4579    
4580                            while (wildcard.indexOf(newSingleWildcardCharacter) != -1) {
4581                                    newSingleWildcardCharacter++;
4582                            }
4583    
4584                            char newMultipleWildcardCharacter =
4585                                    (char)(newSingleWildcardCharacter + 1);
4586    
4587                            while (wildcard.indexOf(newMultipleWildcardCharacter) != -1) {
4588                                    newMultipleWildcardCharacter++;
4589                            }
4590    
4591                            // Purify
4592    
4593                            StringBuilder sb = new StringBuilder(wildcard);
4594    
4595                            for (int i = 0; i < sb.length(); i++) {
4596                                    char c = sb.charAt(i);
4597    
4598                                    if (c == escapeWildcardCharacter) {
4599                                            sb.deleteCharAt(i);
4600                                    }
4601                                    else if (c == singleWildcardCharacter) {
4602                                            sb.setCharAt(i, newSingleWildcardCharacter);
4603                                    }
4604                                    else if (c == multipleWildcardCharacter) {
4605                                            sb.setCharAt(i, newMultipleWildcardCharacter);
4606                                    }
4607                            }
4608    
4609                            wildcard = sb.toString();
4610    
4611                            singleWildcardCharacter = newSingleWildcardCharacter;
4612                            multipleWildcardCharacter = newMultipleWildcardCharacter;
4613                    }
4614    
4615                    // Align head
4616    
4617                    for (index = 0; index < s.length(); index++) {
4618                            if (index >= wildcard.length()) {
4619                                    return false;
4620                            }
4621    
4622                            char c = wildcard.charAt(index);
4623    
4624                            if (c == multipleWildcardCharacter) {
4625                                    break;
4626                            }
4627    
4628                            if ((s.charAt(index) != c) && (c != singleWildcardCharacter)) {
4629                                    return false;
4630                            }
4631                    }
4632    
4633                    // Match body
4634    
4635                    int sIndex = index;
4636                    int wildcardIndex = index;
4637    
4638                    int matchPoint = 0;
4639                    int comparePoint = 0;
4640    
4641                    while (sIndex < s.length()) {
4642                            char c = wildcard.charAt(wildcardIndex);
4643    
4644                            if (c == multipleWildcardCharacter) {
4645                                    if (++wildcardIndex == wildcard.length()) {
4646                                            return true;
4647                                    }
4648    
4649                                    matchPoint = wildcardIndex;
4650                                    comparePoint = sIndex + 1;
4651                            }
4652                            else if ((c == s.charAt(sIndex)) ||
4653                                             (c == singleWildcardCharacter)) {
4654    
4655                                    sIndex++;
4656                                    wildcardIndex++;
4657                            }
4658                            else {
4659                                    wildcardIndex = matchPoint;
4660                                    sIndex = comparePoint++;
4661                            }
4662                    }
4663    
4664                    // Match tail
4665    
4666                    while (wildcardIndex < wildcard.length()) {
4667                            if (wildcard.charAt(wildcardIndex) != multipleWildcardCharacter) {
4668                                    break;
4669                            }
4670    
4671                            wildcardIndex++;
4672                    }
4673    
4674                    if (wildcardIndex == wildcard.length()) {
4675                            return true;
4676                    }
4677                    else {
4678                            return false;
4679                    }
4680            }
4681    
4682            /**
4683             * Wraps the text when it exceeds the <code>80</code> column width limit,
4684             * using a {@link StringPool#NEW_LINE} to break each wrapped line.
4685             *
4686             * @param  text the text to wrap
4687             * @return the wrapped text following the column width limit, or
4688             *         <code>null</code> if the text is <code>null</code>
4689             */
4690            public static String wrap(String text) {
4691                    return wrap(text, 80, StringPool.NEW_LINE);
4692            }
4693    
4694            /**
4695             * Wraps the text when it exceeds the column width limit, using the line
4696             * separator to break each wrapped line.
4697             *
4698             * @param  text the text to wrap
4699             * @param  width the column width limit for the text
4700             * @param  lineSeparator the string to use in breaking each wrapped line
4701             * @return the wrapped text and line separators, following the column width
4702             *         limit, or <code>null</code> if the text is <code>null</code>
4703             */
4704            public static String wrap(String text, int width, String lineSeparator) {
4705                    try {
4706                            return _wrap(text, width, lineSeparator);
4707                    }
4708                    catch (IOException ioe) {
4709                            _log.error(ioe.getMessage());
4710    
4711                            return text;
4712                    }
4713            }
4714    
4715            /**
4716             * Returns <code>false</code> if the character is not whitespace or is equal
4717             * to any of the exception characters.
4718             *
4719             * @param  c the character whose trim-ability is to be determined
4720             * @param  exceptions the whitespace characters to exclude from trimming
4721             * @return <code>false</code> if the character is not whitespace or is equal
4722             *         to any of the exception characters; <code>true</code> otherwise
4723             */
4724            private static boolean _isTrimable(char c, char[] exceptions) {
4725                    for (char exception : exceptions) {
4726                            if (c == exception) {
4727                                    return false;
4728                            }
4729                    }
4730    
4731                    return Character.isWhitespace(c);
4732            }
4733    
4734            private static String _wrap(String text, int width, String lineSeparator)
4735                    throws IOException {
4736    
4737                    if (text == null) {
4738                            return null;
4739                    }
4740    
4741                    StringBundler sb = new StringBundler();
4742    
4743                    UnsyncBufferedReader unsyncBufferedReader = new UnsyncBufferedReader(
4744                            new UnsyncStringReader(text));
4745    
4746                    String s = StringPool.BLANK;
4747    
4748                    while ((s = unsyncBufferedReader.readLine()) != null) {
4749                            if (s.length() == 0) {
4750                                    sb.append(lineSeparator);
4751    
4752                                    continue;
4753                            }
4754    
4755                            int lineLength = 0;
4756    
4757                            String[] tokens = s.split(StringPool.SPACE);
4758    
4759                            for (String token : tokens) {
4760                                    if ((lineLength + token.length() + 1) > width) {
4761                                            if (lineLength > 0) {
4762                                                    sb.append(lineSeparator);
4763                                            }
4764    
4765                                            if (token.length() > width) {
4766                                                    int pos = token.indexOf(CharPool.OPEN_PARENTHESIS);
4767    
4768                                                    if (pos != -1) {
4769                                                            sb.append(token.substring(0, pos + 1));
4770                                                            sb.append(lineSeparator);
4771    
4772                                                            token = token.substring(pos + 1);
4773    
4774                                                            sb.append(token);
4775    
4776                                                            lineLength = token.length();
4777                                                    }
4778                                                    else {
4779                                                            sb.append(token);
4780    
4781                                                            lineLength = token.length();
4782                                                    }
4783                                            }
4784                                            else {
4785                                                    sb.append(token);
4786    
4787                                                    lineLength = token.length();
4788                                            }
4789                                    }
4790                                    else {
4791                                            if (lineLength > 0) {
4792                                                    sb.append(StringPool.SPACE);
4793    
4794                                                    lineLength++;
4795                                            }
4796    
4797                                            sb.append(token);
4798    
4799                                            lineLength += token.length();
4800                                    }
4801                            }
4802    
4803                            sb.append(lineSeparator);
4804                    }
4805    
4806                    return sb.toString();
4807            }
4808    
4809            private static final char[] _HEX_DIGITS = {
4810                    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
4811                    'e', 'f'
4812            };
4813    
4814            private static final char[] _RANDOM_STRING_CHAR_TABLE = {
4815                    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
4816                    'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
4817                    'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
4818                    'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
4819                    'u', 'v', 'w', 'x', 'y', 'z'
4820            };
4821    
4822            private static Log _log = LogFactoryUtil.getLog(StringUtil.class);
4823    
4824            private static String[] _emptyStringArray = new String[0];
4825    
4826    }