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