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, (Character.valueOf(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, (Character.valueOf(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 string <code>s</code> without an
3892             * appended parenthetical suffix. If there is not a space directly before
3893             * the opening parenthesis, the parenthetical suffix is not stripped.
3894             *
3895             * <p>
3896             * Example:
3897             * </p>
3898             *
3899             * <p>
3900             * <pre>
3901             * <code>
3902             * stripParentheticalSuffix("file") returns "file"
3903             * stripParentheticalSuffix("file (0)") returns "file"
3904             * stripParentheticalSuffix("file (0 0)") returns "file"
3905             * stripParentheticalSuffix("file(0)") returns "file(0)"
3906             * </code>
3907             * </pre>
3908             * </p>
3909             *
3910             * @param  s the string from which to strip its parenthetical suffix
3911             * @return a string representing the string <code>s</code> without an
3912             *         appended parenthetical suffix
3913             */
3914            public static String stripParentheticalSuffix(String s) {
3915                    int x = s.lastIndexOf(StringPool.OPEN_PARENTHESIS);
3916                    int y = s.lastIndexOf(StringPool.CLOSE_PARENTHESIS);
3917    
3918                    if ((x == -1) || (y == -1)) {
3919                            return s;
3920                    }
3921    
3922                    if ((x > y) || !s.endsWith(StringPool.CLOSE_PARENTHESIS)) {
3923                            return s;
3924                    }
3925    
3926                    if (s.charAt(x - 1) != CharPool.SPACE) {
3927                            return s;
3928                    }
3929    
3930                    return s.substring(0, x - 1).concat(s.substring(y + 1, s.length()));
3931            }
3932    
3933            /**
3934             * Returns a string representing the Unicode character codes of the
3935             * characters comprising the string <code>s</code>.
3936             *
3937             * <p>
3938             * Example:
3939             * </p>
3940             *
3941             * <p>
3942             * <pre>
3943             * <code>
3944             * toCharCode("a") returns "97"
3945             * toCharCode("b") returns "98"
3946             * toCharCode("c") returns "99"
3947             * toCharCode("What's for lunch?") returns "87104971163911532102111114321081171109910463"
3948             * </code>
3949             * </pre>
3950             * </p>
3951             *
3952             * @param  s the string whose character codes are to be represented
3953             * @return a string representing the Unicode character codes of the
3954             *         characters comprising the string <code>s</code>
3955             */
3956            public static String toCharCode(String s) {
3957                    StringBundler sb = new StringBundler(s.length());
3958    
3959                    for (int i = 0; i < s.length(); i++) {
3960                            sb.append(s.codePointAt(i));
3961                    }
3962    
3963                    return sb.toString();
3964            }
3965    
3966            /**
3967             * Returns a string representing the hexidecimal character code of the
3968             * integer.
3969             *
3970             * <p>
3971             * Example:
3972             * </p>
3973             *
3974             * <p>
3975             * <pre>
3976             * <code>
3977             * toHexString(10) returns "a"
3978             * toHexString(15) returns "f"
3979             * toHexString(10995) returns "2af3"
3980             * </code>
3981             * </pre>
3982             * </p>
3983             *
3984             * @param  i the integer to convert
3985             * @return a string representing the hexidecimal character code of the
3986             *         integer
3987             */
3988            public static String toHexString(int i) {
3989                    char[] buffer = new char[8];
3990    
3991                    int index = 8;
3992    
3993                    do {
3994                            buffer[--index] = HEX_DIGITS[i & 15];
3995    
3996                            i >>>= 4;
3997                    }
3998                    while (i != 0);
3999    
4000                    return new String(buffer, index, 8 - index);
4001            }
4002    
4003            /**
4004             * Returns a string representing the hexidecimal character code of the long
4005             * integer.
4006             *
4007             * <p>
4008             * Example:
4009             * </p>
4010             *
4011             * <p>
4012             * <pre>
4013             * <code>
4014             * toHexString(12345678910L) returns "2dfdc1c3e"
4015             * </code>
4016             * </pre>
4017             * </p>
4018             *
4019             * @param  l the long integer to convert
4020             * @return a string representing the hexidecimal character code of the long
4021             *         integer
4022             */
4023            public static String toHexString(long l) {
4024                    char[] buffer = new char[16];
4025    
4026                    int index = 16;
4027    
4028                    do {
4029                            buffer[--index] = HEX_DIGITS[(int) (l & 15)];
4030    
4031                            l >>>= 4;
4032                    }
4033                    while (l != 0);
4034    
4035                    return new String(buffer, index, 16 - index);
4036            }
4037    
4038            /**
4039             * Returns a string representing the hexidecimal character code of the
4040             * <code>Integer</code> or <code>Long</code> object type. If the object is
4041             * not an instance of these types, the object's original value is returned.
4042             *
4043             * @param  obj the object to convert
4044             * @return a string representing the hexidecimal character code of the
4045             *         object
4046             */
4047            public static String toHexString(Object obj) {
4048                    if (obj instanceof Integer) {
4049                            return toHexString(((Integer)obj).intValue());
4050                    }
4051                    else if (obj instanceof Long) {
4052                            return toHexString(((Long)obj).longValue());
4053                    }
4054                    else {
4055                            return String.valueOf(obj);
4056                    }
4057            }
4058    
4059            /**
4060             * Converts all of the characters in the string to lower case, based on the
4061             * portal instance's default locale.
4062             *
4063             * @param  s the string to convert
4064             * @return the string, converted to lower case, or <code>null</code> if the
4065             *         string is <code>null</code>
4066             */
4067            public static String toLowerCase(String s) {
4068                    return toLowerCase(s, null);
4069            }
4070    
4071            /**
4072             * Converts all of the characters in the string to lower case, based on the
4073             * locale.
4074             *
4075             * @param  s the string to convert
4076             * @param  locale apply this locale's rules
4077             * @return the string, converted to lower case, or <code>null</code> if the
4078             *         string is <code>null</code>
4079             */
4080            public static String toLowerCase(String s, Locale locale) {
4081                    if (s == null) {
4082                            return null;
4083                    }
4084    
4085                    StringBuilder sb = null;
4086    
4087                    for (int i = 0; i < s.length(); i++) {
4088                            char c = s.charAt(i);
4089    
4090                            if (c > 127) {
4091    
4092                                    // Found non-ascii char, fallback to the slow unicode detection
4093    
4094                                    if (locale == null) {
4095                                            locale = LocaleUtil.getDefault();
4096                                    }
4097    
4098                                    return s.toLowerCase(locale);
4099                            }
4100    
4101                            if ((c >= 'A') && (c <= 'Z')) {
4102                                    if (sb == null) {
4103                                            sb = new StringBuilder(s);
4104                                    }
4105    
4106                                    sb.setCharAt(i, (char)(c + 32));
4107                            }
4108                    }
4109    
4110                    if (sb == null) {
4111                            return s;
4112                    }
4113    
4114                    return sb.toString();
4115            }
4116    
4117            /**
4118             * Converts all of the characters in the string to upper case, based on the
4119             * portal instance's default locale.
4120             *
4121             * @param  s the string to convert
4122             * @return the string, converted to upper case, or <code>null</code> if the
4123             *         string is <code>null</code>
4124             */
4125            public static String toUpperCase(String s) {
4126                    return toUpperCase(s, null);
4127            }
4128    
4129            /**
4130             * Converts all of the characters in the string to upper case, based on the
4131             * locale.
4132             *
4133             * @param  s the string to convert
4134             * @param  locale apply this locale's rules
4135             * @return the string, converted to upper case, or <code>null</code> if the
4136             *         string is <code>null</code>
4137             */
4138            public static String toUpperCase(String s, Locale locale) {
4139                    if (s == null) {
4140                            return null;
4141                    }
4142    
4143                    StringBuilder sb = null;
4144    
4145                    for (int i = 0; i < s.length(); i++) {
4146                            char c = s.charAt(i);
4147    
4148                            if (c > 127) {
4149    
4150                                    // Found non-ascii char, fallback to the slow unicode detection
4151    
4152                                    if (locale == null) {
4153                                            locale = LocaleUtil.getDefault();
4154                                    }
4155    
4156                                    return s.toUpperCase(locale);
4157                            }
4158    
4159                            if ((c >= 'a') && (c <= 'z')) {
4160                                    if (sb == null) {
4161                                            sb = new StringBuilder(s);
4162                                    }
4163    
4164                                    sb.setCharAt(i, (char)(c - 32));
4165                            }
4166                    }
4167    
4168                    if (sb == null) {
4169                            return s;
4170                    }
4171    
4172                    return sb.toString();
4173            }
4174    
4175            /**
4176             * Trims all leading and trailing whitespace from the string.
4177             *
4178             * @param  s the original string
4179             * @return a string representing the original string with all leading and
4180             *         trailing whitespace removed
4181             */
4182            public static String trim(String s) {
4183                    if (s == null) {
4184                            return null;
4185                    }
4186    
4187                    if (s.length() == 0) {
4188                            return s;
4189                    }
4190    
4191                    int len = s.length();
4192    
4193                    int x = len;
4194    
4195                    for (int i = 0; i < len; i++) {
4196                            char c = s.charAt(i);
4197    
4198                            if (!Character.isWhitespace(c)) {
4199                                    x = i;
4200    
4201                                    break;
4202                            }
4203                    }
4204    
4205                    if (x == len) {
4206                            return StringPool.BLANK;
4207                    }
4208    
4209                    int y = x + 1;
4210    
4211                    for (int i = len - 1; i > x; i--) {
4212                            char c = s.charAt(i);
4213    
4214                            if (!Character.isWhitespace(c)) {
4215                                    y = i + 1;
4216    
4217                                    break;
4218                            }
4219                    }
4220    
4221                    if ((x == 0) && (y == len)) {
4222                            return s;
4223                    }
4224    
4225                    return s.substring(x, y);
4226            }
4227    
4228            /**
4229             * Trims leading and trailing whitespace from the string, up to but not
4230             * including the whitespace character specified by <code>c</code>.
4231             *
4232             * <p>
4233             * Examples:
4234             * </p>
4235             *
4236             * <p>
4237             * <pre>
4238             * <code>
4239             * trim(" \tHey\t ", '\t') returns "\tHey\t"
4240             * trim(" \t Hey \t ", '\t') returns "\t Hey \t"
4241             * </code>
4242             * </pre>
4243             * </p>
4244             *
4245             * @param  s the original string
4246             * @param  c the whitespace character to limit trimming
4247             * @return a string representing the original string with leading and
4248             *         trailing whitespace removed, up to but not including the
4249             *         whitespace character specified by <code>c</code>
4250             */
4251            public static String trim(String s, char c) {
4252                    return trim(s, new char[] {c});
4253            }
4254    
4255            /**
4256             * Trims leading and trailing whitespace from the string, up to but not
4257             * including the whitespace characters specified by <code>exceptions</code>.
4258             *
4259             * @param  s the original string
4260             * @param  exceptions the whitespace characters to limit trimming
4261             * @return a string representing the original string with leading and
4262             *         trailing whitespace removed, up to but not including the
4263             *         whitespace characters specified by <code>exceptions</code>
4264             */
4265            public static String trim(String s, char[] exceptions) {
4266                    if (s == null) {
4267                            return null;
4268                    }
4269    
4270                    if (s.length() == 0) {
4271                            return s;
4272                    }
4273    
4274                    if (ArrayUtil.isEmpty(exceptions)) {
4275                            return trim(s);
4276                    }
4277    
4278                    int len = s.length();
4279                    int x = len;
4280    
4281                    for (int i = 0; i < len; i++) {
4282                            char c = s.charAt(i);
4283    
4284                            if (!_isTrimable(c, exceptions)) {
4285                                    x = i;
4286    
4287                                    break;
4288                            }
4289                    }
4290    
4291                    if (x == len) {
4292                            return StringPool.BLANK;
4293                    }
4294    
4295                    int y = x + 1;
4296    
4297                    for (int i = len - 1; i > x; i--) {
4298                            char c = s.charAt(i);
4299    
4300                            if (!_isTrimable(c, exceptions)) {
4301                                    y = i + 1;
4302    
4303                                    break;
4304                            }
4305                    }
4306    
4307                    if ((x == 0) && (y == len)) {
4308                            return s;
4309                    }
4310                    else {
4311                            return s.substring(x, y);
4312                    }
4313            }
4314    
4315            /**
4316             * Trims all leading whitespace from the string.
4317             *
4318             * @param  s the original string
4319             * @return a string representing the original string with all leading
4320             *         whitespace removed
4321             */
4322            public static String trimLeading(String s) {
4323                    if (s == null) {
4324                            return null;
4325                    }
4326    
4327                    if (s.length() == 0) {
4328                            return s;
4329                    }
4330    
4331                    int len = s.length();
4332                    int x = len;
4333    
4334                    for (int i = 0; i < len; i++) {
4335                            char c = s.charAt(i);
4336    
4337                            if (!Character.isWhitespace(c)) {
4338                                    x = i;
4339    
4340                                    break;
4341                            }
4342                    }
4343    
4344                    if (x == len) {
4345                            return StringPool.BLANK;
4346                    }
4347                    else if (x == 0) {
4348                            return s;
4349                    }
4350                    else {
4351                            return s.substring(x);
4352                    }
4353            }
4354    
4355            /**
4356             * Trims leading whitespace from the string, up to but not including the
4357             * whitespace character specified by <code>c</code>.
4358             *
4359             * @param  s the original string
4360             * @param  c the whitespace character to limit trimming
4361             * @return a string representing the original string with leading whitespace
4362             *         removed, up to but not including the whitespace character
4363             *         specified by <code>c</code>
4364             */
4365            public static String trimLeading(String s, char c) {
4366                    return trimLeading(s, new char[] {c});
4367            }
4368    
4369            /**
4370             * Trims leading whitespace from the string, up to but not including the
4371             * whitespace characters specified by <code>exceptions</code>.
4372             *
4373             * @param  s the original string
4374             * @param  exceptions the whitespace characters to limit trimming
4375             * @return a string representing the original string with leading whitespace
4376             *         removed, up to but not including the whitespace characters
4377             *         specified by <code>exceptions</code>
4378             */
4379            public static String trimLeading(String s, char[] exceptions) {
4380                    if (s == null) {
4381                            return null;
4382                    }
4383    
4384                    if (s.length() == 0) {
4385                            return s;
4386                    }
4387    
4388                    if (ArrayUtil.isEmpty(exceptions)) {
4389                            return trimLeading(s);
4390                    }
4391    
4392                    int len = s.length();
4393                    int x = len;
4394    
4395                    for (int i = 0; i < len; i++) {
4396                            char c = s.charAt(i);
4397    
4398                            if (!_isTrimable(c, exceptions)) {
4399                                    x = i;
4400    
4401                                    break;
4402                            }
4403                    }
4404    
4405                    if (x == len) {
4406                            return StringPool.BLANK;
4407                    }
4408                    else if (x == 0) {
4409                            return s;
4410                    }
4411                    else {
4412                            return s.substring(x);
4413                    }
4414            }
4415    
4416            /**
4417             * Trims all trailing whitespace from the string.
4418             *
4419             * @param  s the original string
4420             * @return a string representing the original string with all trailing
4421             *         whitespace removed
4422             */
4423            public static String trimTrailing(String s) {
4424                    if (s == null) {
4425                            return null;
4426                    }
4427    
4428                    if (s.length() == 0) {
4429                            return s;
4430                    }
4431    
4432                    int len = s.length();
4433                    int x = 0;
4434    
4435                    for (int i = len - 1; i >= 0; i--) {
4436                            char c = s.charAt(i);
4437    
4438                            if (!Character.isWhitespace(c)) {
4439                                    x = i + 1;
4440    
4441                                    break;
4442                            }
4443                    }
4444    
4445                    if (x == 0) {
4446                            return StringPool.BLANK;
4447                    }
4448                    else if (x == len) {
4449                            return s;
4450                    }
4451                    else {
4452                            return s.substring(0, x);
4453                    }
4454            }
4455    
4456            /**
4457             * Trims trailing whitespace from the string, up to but not including the
4458             * whitespace character specified by <code>c</code>.
4459             *
4460             * @param  s the original string
4461             * @param  c the whitespace character to limit trimming
4462             * @return a string representing the original string with trailing
4463             *         whitespace removed, up to but not including the whitespace
4464             *         character specified by <code>c</code>
4465             */
4466            public static String trimTrailing(String s, char c) {
4467                    return trimTrailing(s, new char[] {c});
4468            }
4469    
4470            /**
4471             * Trims trailing whitespace from the string, up to but not including the
4472             * whitespace characters specified by <code>exceptions</code>.
4473             *
4474             * @param  s the original string
4475             * @param  exceptions the whitespace characters to limit trimming
4476             * @return a string representing the original string with trailing
4477             *         whitespace removed, up to but not including the whitespace
4478             *         characters specified by <code>exceptions</code>
4479             */
4480            public static String trimTrailing(String s, char[] exceptions) {
4481                    if (s == null) {
4482                            return null;
4483                    }
4484    
4485                    if (s.length() == 0) {
4486                            return s;
4487                    }
4488    
4489                    if (ArrayUtil.isEmpty(exceptions)) {
4490                            return trimTrailing(s);
4491                    }
4492    
4493                    int len = s.length();
4494                    int x = 0;
4495    
4496                    for (int i = len - 1; i >= 0; i--) {
4497                            char c = s.charAt(i);
4498    
4499                            if (!_isTrimable(c, exceptions)) {
4500                                    x = i + 1;
4501    
4502                                    break;
4503                            }
4504                    }
4505    
4506                    if (x == 0) {
4507                            return StringPool.BLANK;
4508                    }
4509                    else if (x == len) {
4510                            return s;
4511                    }
4512                    else {
4513                            return s.substring(0, x);
4514                    }
4515            }
4516    
4517            /**
4518             * Removes leading and trailing double and single quotation marks from the
4519             * string.
4520             *
4521             * @param  s the original string
4522             * @return a string representing the original string with leading and
4523             *         trailing double and single quotation marks removed, or the
4524             *         original string if the original string is a <code>null</code> or
4525             *         empty
4526             */
4527            public static String unquote(String s) {
4528                    if (Validator.isNull(s)) {
4529                            return s;
4530                    }
4531    
4532                    if ((s.charAt(0) == CharPool.APOSTROPHE) &&
4533                            (s.charAt(s.length() - 1) == CharPool.APOSTROPHE)) {
4534    
4535                            return s.substring(1, s.length() - 1);
4536                    }
4537                    else if ((s.charAt(0) == CharPool.QUOTE) &&
4538                                     (s.charAt(s.length() - 1) == CharPool.QUOTE)) {
4539    
4540                            return s.substring(1, s.length() - 1);
4541                    }
4542    
4543                    return s;
4544            }
4545    
4546            /**
4547             * Converts all of the characters in the string to upper case.
4548             *
4549             * @param  s the string to convert
4550             * @return the string, converted to upper-case, or <code>null</code> if the
4551             *         string is <code>null</code>
4552             * @see    String#toUpperCase()
4553             */
4554            public static String upperCase(String s) {
4555                    return toUpperCase(s);
4556            }
4557    
4558            /**
4559             * Converts the first character of the string to upper case.
4560             *
4561             * @param  s the string whose first character is to be converted
4562             * @return the string, with its first character converted to upper-case
4563             */
4564            public static String upperCaseFirstLetter(String s) {
4565                    char[] chars = s.toCharArray();
4566    
4567                    if ((chars[0] >= 97) && (chars[0] <= 122)) {
4568                            chars[0] = (char)(chars[0] - 32);
4569                    }
4570    
4571                    return new String(chars);
4572            }
4573    
4574            /**
4575             * Returns the string value of the object.
4576             *
4577             * @param  obj the object whose string value is to be returned
4578             * @return the string value of the object
4579             * @see    String#valueOf(Object obj)
4580             */
4581            public static String valueOf(Object obj) {
4582                    return String.valueOf(obj);
4583            }
4584    
4585            /**
4586             * Returns <code>true</code> if the string matches the wildcard pattern.
4587             *
4588             * <p>
4589             * For example, with the following initialized variables:
4590             * </p>
4591             *
4592             * <p>
4593             * <pre>
4594             * <code>
4595             * String s = "*master";
4596             * String wildcard = "/*m?st*";
4597             * char singleWildcardCharacter = '?';
4598             * char multipleWildcardCharacter = '*';
4599             * char escapeWildcardCharacter = '/';
4600             * boolean caseSensitive = false;
4601             * </code>
4602             * </pre>
4603             * </p>
4604             *
4605             * <p>
4606             * <code>wildcardMatches(s, wildcard, singleWildcardCharacter,
4607             * multipleWildcardCharacter, escapeWildcardCharacter, caseSensitive)</code>
4608             * returns <code>true</code>
4609             * </p>
4610             *
4611             * @param  s the string to be checked
4612             * @param  wildcard the wildcard pattern to match
4613             * @param  singleWildcardCharacter the char used to match exactly one
4614             *         character
4615             * @param  multipleWildcardCharacter the char used to match <code>0</code>
4616             *         or more characters
4617             * @param  escapeWildcardCharacter the char placed in front of a wildcard
4618             *         character to indicate that it should be interpreted as a regular
4619             *         character
4620             * @param  caseSensitive whether to use case sensitivity
4621             * @return <code>true</code> if the string matches the wildcard pattern;
4622             *         <code>false</code> otherwise
4623             */
4624            public static boolean wildcardMatches(
4625                    String s, String wildcard, char singleWildcardCharacter,
4626                    char multipleWildcardCharacter, char escapeWildcardCharacter,
4627                    boolean caseSensitive) {
4628    
4629                    if (!caseSensitive) {
4630                            s = toLowerCase(s);
4631                            wildcard = toLowerCase(wildcard);
4632                    }
4633    
4634                    // Update the wildcard, single whildcard character, and multiple
4635                    // wildcard character so that they no longer have escaped wildcard
4636                    // characters
4637    
4638                    int index = wildcard.indexOf(escapeWildcardCharacter);
4639    
4640                    if (index != -1) {
4641    
4642                            // Search for safe wildcard replacement
4643    
4644                            char newSingleWildcardCharacter = 0;
4645    
4646                            while (wildcard.indexOf(newSingleWildcardCharacter) != -1) {
4647                                    newSingleWildcardCharacter++;
4648                            }
4649    
4650                            char newMultipleWildcardCharacter =
4651                                    (char)(newSingleWildcardCharacter + 1);
4652    
4653                            while (wildcard.indexOf(newMultipleWildcardCharacter) != -1) {
4654                                    newMultipleWildcardCharacter++;
4655                            }
4656    
4657                            // Purify
4658    
4659                            StringBuilder sb = new StringBuilder(wildcard);
4660    
4661                            for (int i = 0; i < sb.length(); i++) {
4662                                    char c = sb.charAt(i);
4663    
4664                                    if (c == escapeWildcardCharacter) {
4665                                            sb.deleteCharAt(i);
4666                                    }
4667                                    else if (c == singleWildcardCharacter) {
4668                                            sb.setCharAt(i, newSingleWildcardCharacter);
4669                                    }
4670                                    else if (c == multipleWildcardCharacter) {
4671                                            sb.setCharAt(i, newMultipleWildcardCharacter);
4672                                    }
4673                            }
4674    
4675                            wildcard = sb.toString();
4676    
4677                            singleWildcardCharacter = newSingleWildcardCharacter;
4678                            multipleWildcardCharacter = newMultipleWildcardCharacter;
4679                    }
4680    
4681                    // Align head
4682    
4683                    for (index = 0; index < s.length(); index++) {
4684                            if (index >= wildcard.length()) {
4685                                    return false;
4686                            }
4687    
4688                            char c = wildcard.charAt(index);
4689    
4690                            if (c == multipleWildcardCharacter) {
4691                                    break;
4692                            }
4693    
4694                            if ((s.charAt(index) != c) && (c != singleWildcardCharacter)) {
4695                                    return false;
4696                            }
4697                    }
4698    
4699                    // Match body
4700    
4701                    int sIndex = index;
4702                    int wildcardIndex = index;
4703    
4704                    int matchPoint = 0;
4705                    int comparePoint = 0;
4706    
4707                    while (sIndex < s.length()) {
4708                            char c = wildcard.charAt(wildcardIndex);
4709    
4710                            if (c == multipleWildcardCharacter) {
4711                                    if (++wildcardIndex == wildcard.length()) {
4712                                            return true;
4713                                    }
4714    
4715                                    matchPoint = wildcardIndex;
4716                                    comparePoint = sIndex + 1;
4717                            }
4718                            else if ((c == s.charAt(sIndex)) ||
4719                                             (c == singleWildcardCharacter)) {
4720    
4721                                    sIndex++;
4722                                    wildcardIndex++;
4723                            }
4724                            else {
4725                                    wildcardIndex = matchPoint;
4726                                    sIndex = comparePoint++;
4727                            }
4728                    }
4729    
4730                    // Match tail
4731    
4732                    while (wildcardIndex < wildcard.length()) {
4733                            if (wildcard.charAt(wildcardIndex) != multipleWildcardCharacter) {
4734                                    break;
4735                            }
4736    
4737                            wildcardIndex++;
4738                    }
4739    
4740                    if (wildcardIndex == wildcard.length()) {
4741                            return true;
4742                    }
4743                    else {
4744                            return false;
4745                    }
4746            }
4747    
4748            /**
4749             * Wraps the text when it exceeds the <code>80</code> column width limit,
4750             * using a {@link StringPool#NEW_LINE} to break each wrapped line.
4751             *
4752             * @param  text the text to wrap
4753             * @return the wrapped text following the column width limit, or
4754             *         <code>null</code> if the text is <code>null</code>
4755             */
4756            public static String wrap(String text) {
4757                    return wrap(text, 80, StringPool.NEW_LINE);
4758            }
4759    
4760            /**
4761             * Wraps the text when it exceeds the column width limit, using the line
4762             * separator to break each wrapped line.
4763             *
4764             * @param  text the text to wrap
4765             * @param  width the column width limit for the text
4766             * @param  lineSeparator the string to use in breaking each wrapped line
4767             * @return the wrapped text and line separators, following the column width
4768             *         limit, or <code>null</code> if the text is <code>null</code>
4769             */
4770            public static String wrap(String text, int width, String lineSeparator) {
4771                    try {
4772                            return _wrap(text, width, lineSeparator);
4773                    }
4774                    catch (IOException ioe) {
4775                            _log.error(ioe.getMessage());
4776    
4777                            return text;
4778                    }
4779            }
4780    
4781            protected static final char[] HEX_DIGITS = {
4782                    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
4783                    'e', 'f'
4784            };
4785    
4786            /**
4787             * Returns <code>false</code> if the character is not whitespace or is equal
4788             * to any of the exception characters.
4789             *
4790             * @param  c the character whose trim-ability is to be determined
4791             * @param  exceptions the whitespace characters to exclude from trimming
4792             * @return <code>false</code> if the character is not whitespace or is equal
4793             *         to any of the exception characters; <code>true</code> otherwise
4794             */
4795            private static boolean _isTrimable(char c, char[] exceptions) {
4796                    for (char exception : exceptions) {
4797                            if (c == exception) {
4798                                    return false;
4799                            }
4800                    }
4801    
4802                    return Character.isWhitespace(c);
4803            }
4804    
4805            private static String _wrap(String text, int width, String lineSeparator)
4806                    throws IOException {
4807    
4808                    if (text == null) {
4809                            return null;
4810                    }
4811    
4812                    StringBundler sb = new StringBundler();
4813    
4814                    UnsyncBufferedReader unsyncBufferedReader = new UnsyncBufferedReader(
4815                            new UnsyncStringReader(text));
4816    
4817                    String s = StringPool.BLANK;
4818    
4819                    while ((s = unsyncBufferedReader.readLine()) != null) {
4820                            if (s.length() == 0) {
4821                                    sb.append(lineSeparator);
4822    
4823                                    continue;
4824                            }
4825    
4826                            int lineLength = 0;
4827    
4828                            String[] tokens = s.split(StringPool.SPACE);
4829    
4830                            for (String token : tokens) {
4831                                    if ((lineLength + token.length() + 1) > width) {
4832                                            if (lineLength > 0) {
4833                                                    sb.append(lineSeparator);
4834                                            }
4835    
4836                                            if (token.length() > width) {
4837                                                    int pos = token.indexOf(CharPool.OPEN_PARENTHESIS);
4838    
4839                                                    if (pos != -1) {
4840                                                            sb.append(token.substring(0, pos + 1));
4841                                                            sb.append(lineSeparator);
4842    
4843                                                            token = token.substring(pos + 1);
4844    
4845                                                            sb.append(token);
4846    
4847                                                            lineLength = token.length();
4848                                                    }
4849                                                    else {
4850                                                            sb.append(token);
4851    
4852                                                            lineLength = token.length();
4853                                                    }
4854                                            }
4855                                            else {
4856                                                    sb.append(token);
4857    
4858                                                    lineLength = token.length();
4859                                            }
4860                                    }
4861                                    else {
4862                                            if (lineLength > 0) {
4863                                                    sb.append(StringPool.SPACE);
4864    
4865                                                    lineLength++;
4866                                            }
4867    
4868                                            sb.append(token);
4869    
4870                                            lineLength += token.length();
4871                                    }
4872                            }
4873    
4874                            sb.append(lineSeparator);
4875                    }
4876    
4877                    return sb.toString();
4878            }
4879    
4880            private static final char[] _RANDOM_STRING_CHAR_TABLE = {
4881                    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
4882                    'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
4883                    'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
4884                    'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
4885                    'u', 'v', 'w', 'x', 'y', 'z'
4886            };
4887    
4888            private static final Log _log = LogFactoryUtil.getLog(StringUtil.class);
4889    
4890            private static final String[] _emptyStringArray = new String[0];
4891    
4892    }