001/*
002 * Copyright 2002-2019 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.util;
018
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.Collection;
022import java.util.Collections;
023import java.util.Enumeration;
024import java.util.Iterator;
025import java.util.LinkedHashSet;
026import java.util.LinkedList;
027import java.util.List;
028import java.util.Locale;
029import java.util.Properties;
030import java.util.Set;
031import java.util.StringTokenizer;
032import java.util.TimeZone;
033
034/**
035 * Miscellaneous {@link String} utility methods.
036 *
037 * <p>Mainly for internal use within the framework; consider
038 * <a href="https://commons.apache.org/proper/commons-lang/">Apache's Commons Lang</a>
039 * for a more comprehensive suite of {@code String} utilities.
040 *
041 * <p>This class delivers some simple functionality that should really be
042 * provided by the core Java {@link String} and {@link StringBuilder}
043 * classes. It also provides easy-to-use methods to convert between
044 * delimited strings, such as CSV strings, and collections and arrays.
045 *
046 * @author Rod Johnson
047 * @author Juergen Hoeller
048 * @author Keith Donald
049 * @author Rob Harrop
050 * @author Rick Evans
051 * @author Arjen Poutsma
052 * @author Sam Brannen
053 * @author Brian Clozel
054 * @since 16 April 2001
055 */
056public abstract class StringUtils {
057
058        private static final String FOLDER_SEPARATOR = "/";
059
060        private static final String WINDOWS_FOLDER_SEPARATOR = "\\";
061
062        private static final String TOP_PATH = "..";
063
064        private static final String CURRENT_PATH = ".";
065
066        private static final char EXTENSION_SEPARATOR = '.';
067
068
069        //---------------------------------------------------------------------
070        // General convenience methods for working with Strings
071        //---------------------------------------------------------------------
072
073        /**
074         * Check whether the given {@code String} is empty.
075         * <p>This method accepts any Object as an argument, comparing it to
076         * {@code null} and the empty String. As a consequence, this method
077         * will never return {@code true} for a non-null non-String object.
078         * <p>The Object signature is useful for general attribute handling code
079         * that commonly deals with Strings but generally has to iterate over
080         * Objects since attributes may e.g. be primitive value objects as well.
081         * @param str the candidate String
082         * @since 3.2.1
083         */
084        public static boolean isEmpty(Object str) {
085                return (str == null || "".equals(str));
086        }
087
088        /**
089         * Check that the given {@code CharSequence} is neither {@code null} nor
090         * of length 0.
091         * <p>Note: this method returns {@code true} for a {@code CharSequence}
092         * that purely consists of whitespace.
093         * <p><pre class="code">
094         * StringUtils.hasLength(null) = false
095         * StringUtils.hasLength("") = false
096         * StringUtils.hasLength(" ") = true
097         * StringUtils.hasLength("Hello") = true
098         * </pre>
099         * @param str the {@code CharSequence} to check (may be {@code null})
100         * @return {@code true} if the {@code CharSequence} is not {@code null} and has length
101         * @see #hasText(String)
102         */
103        public static boolean hasLength(CharSequence str) {
104                return (str != null && str.length() > 0);
105        }
106
107        /**
108         * Check that the given {@code String} is neither {@code null} nor of length 0.
109         * <p>Note: this method returns {@code true} for a {@code String} that
110         * purely consists of whitespace.
111         * @param str the {@code String} to check (may be {@code null})
112         * @return {@code true} if the {@code String} is not {@code null} and has length
113         * @see #hasLength(CharSequence)
114         * @see #hasText(String)
115         */
116        public static boolean hasLength(String str) {
117                return (str != null && !str.isEmpty());
118        }
119
120        /**
121         * Check whether the given {@code CharSequence} contains actual <em>text</em>.
122         * <p>More specifically, this method returns {@code true} if the
123         * {@code CharSequence} is not {@code null}, its length is greater than
124         * 0, and it contains at least one non-whitespace character.
125         * <p><pre class="code">
126         * StringUtils.hasText(null) = false
127         * StringUtils.hasText("") = false
128         * StringUtils.hasText(" ") = false
129         * StringUtils.hasText("12345") = true
130         * StringUtils.hasText(" 12345 ") = true
131         * </pre>
132         * @param str the {@code CharSequence} to check (may be {@code null})
133         * @return {@code true} if the {@code CharSequence} is not {@code null},
134         * its length is greater than 0, and it does not contain whitespace only
135         * @see Character#isWhitespace
136         */
137        public static boolean hasText(CharSequence str) {
138                return (hasLength(str) && containsText(str));
139        }
140
141        /**
142         * Check whether the given {@code String} contains actual <em>text</em>.
143         * <p>More specifically, this method returns {@code true} if the
144         * {@code String} is not {@code null}, its length is greater than 0,
145         * and it contains at least one non-whitespace character.
146         * @param str the {@code String} to check (may be {@code null})
147         * @return {@code true} if the {@code String} is not {@code null}, its
148         * length is greater than 0, and it does not contain whitespace only
149         * @see #hasText(CharSequence)
150         */
151        public static boolean hasText(String str) {
152                return (hasLength(str) && containsText(str));
153        }
154
155        private static boolean containsText(CharSequence str) {
156                int strLen = str.length();
157                for (int i = 0; i < strLen; i++) {
158                        if (!Character.isWhitespace(str.charAt(i))) {
159                                return true;
160                        }
161                }
162                return false;
163        }
164
165        /**
166         * Check whether the given {@code CharSequence} contains any whitespace characters.
167         * @param str the {@code CharSequence} to check (may be {@code null})
168         * @return {@code true} if the {@code CharSequence} is not empty and
169         * contains at least 1 whitespace character
170         * @see Character#isWhitespace
171         */
172        public static boolean containsWhitespace(CharSequence str) {
173                if (!hasLength(str)) {
174                        return false;
175                }
176
177                int strLen = str.length();
178                for (int i = 0; i < strLen; i++) {
179                        if (Character.isWhitespace(str.charAt(i))) {
180                                return true;
181                        }
182                }
183                return false;
184        }
185
186        /**
187         * Check whether the given {@code String} contains any whitespace characters.
188         * @param str the {@code String} to check (may be {@code null})
189         * @return {@code true} if the {@code String} is not empty and
190         * contains at least 1 whitespace character
191         * @see #containsWhitespace(CharSequence)
192         */
193        public static boolean containsWhitespace(String str) {
194                return containsWhitespace((CharSequence) str);
195        }
196
197        /**
198         * Trim leading and trailing whitespace from the given {@code String}.
199         * @param str the {@code String} to check
200         * @return the trimmed {@code String}
201         * @see java.lang.Character#isWhitespace
202         */
203        public static String trimWhitespace(String str) {
204                if (!hasLength(str)) {
205                        return str;
206                }
207
208                StringBuilder sb = new StringBuilder(str);
209                while (sb.length() > 0 && Character.isWhitespace(sb.charAt(0))) {
210                        sb.deleteCharAt(0);
211                }
212                while (sb.length() > 0 && Character.isWhitespace(sb.charAt(sb.length() - 1))) {
213                        sb.deleteCharAt(sb.length() - 1);
214                }
215                return sb.toString();
216        }
217
218        /**
219         * Trim <i>all</i> whitespace from the given {@code String}:
220         * leading, trailing, and in between characters.
221         * @param str the {@code String} to check
222         * @return the trimmed {@code String}
223         * @see java.lang.Character#isWhitespace
224         */
225        public static String trimAllWhitespace(String str) {
226                if (!hasLength(str)) {
227                        return str;
228                }
229
230                int len = str.length();
231                StringBuilder sb = new StringBuilder(str.length());
232                for (int i = 0; i < len; i++) {
233                        char c = str.charAt(i);
234                        if (!Character.isWhitespace(c)) {
235                                sb.append(c);
236                        }
237                }
238                return sb.toString();
239        }
240
241        /**
242         * Trim leading whitespace from the given {@code String}.
243         * @param str the {@code String} to check
244         * @return the trimmed {@code String}
245         * @see java.lang.Character#isWhitespace
246         */
247        public static String trimLeadingWhitespace(String str) {
248                if (!hasLength(str)) {
249                        return str;
250                }
251
252                StringBuilder sb = new StringBuilder(str);
253                while (sb.length() > 0 && Character.isWhitespace(sb.charAt(0))) {
254                        sb.deleteCharAt(0);
255                }
256                return sb.toString();
257        }
258
259        /**
260         * Trim trailing whitespace from the given {@code String}.
261         * @param str the {@code String} to check
262         * @return the trimmed {@code String}
263         * @see java.lang.Character#isWhitespace
264         */
265        public static String trimTrailingWhitespace(String str) {
266                if (!hasLength(str)) {
267                        return str;
268                }
269
270                StringBuilder sb = new StringBuilder(str);
271                while (sb.length() > 0 && Character.isWhitespace(sb.charAt(sb.length() - 1))) {
272                        sb.deleteCharAt(sb.length() - 1);
273                }
274                return sb.toString();
275        }
276
277        /**
278         * Trim all occurrences of the supplied leading character from the given {@code String}.
279         * @param str the {@code String} to check
280         * @param leadingCharacter the leading character to be trimmed
281         * @return the trimmed {@code String}
282         */
283        public static String trimLeadingCharacter(String str, char leadingCharacter) {
284                if (!hasLength(str)) {
285                        return str;
286                }
287
288                StringBuilder sb = new StringBuilder(str);
289                while (sb.length() > 0 && sb.charAt(0) == leadingCharacter) {
290                        sb.deleteCharAt(0);
291                }
292                return sb.toString();
293        }
294
295        /**
296         * Trim all occurrences of the supplied trailing character from the given {@code String}.
297         * @param str the {@code String} to check
298         * @param trailingCharacter the trailing character to be trimmed
299         * @return the trimmed {@code String}
300         */
301        public static String trimTrailingCharacter(String str, char trailingCharacter) {
302                if (!hasLength(str)) {
303                        return str;
304                }
305
306                StringBuilder sb = new StringBuilder(str);
307                while (sb.length() > 0 && sb.charAt(sb.length() - 1) == trailingCharacter) {
308                        sb.deleteCharAt(sb.length() - 1);
309                }
310                return sb.toString();
311        }
312
313        /**
314         * Test if the given {@code String} starts with the specified prefix,
315         * ignoring upper/lower case.
316         * @param str the {@code String} to check
317         * @param prefix the prefix to look for
318         * @see java.lang.String#startsWith
319         */
320        public static boolean startsWithIgnoreCase(String str, String prefix) {
321                return (str != null && prefix != null && str.length() >= prefix.length() &&
322                                str.regionMatches(true, 0, prefix, 0, prefix.length()));
323        }
324
325        /**
326         * Test if the given {@code String} ends with the specified suffix,
327         * ignoring upper/lower case.
328         * @param str the {@code String} to check
329         * @param suffix the suffix to look for
330         * @see java.lang.String#endsWith
331         */
332        public static boolean endsWithIgnoreCase(String str, String suffix) {
333                return (str != null && suffix != null && str.length() >= suffix.length() &&
334                                str.regionMatches(true, str.length() - suffix.length(), suffix, 0, suffix.length()));
335        }
336
337        /**
338         * Test whether the given string matches the given substring
339         * at the given index.
340         * @param str the original string (or StringBuilder)
341         * @param index the index in the original string to start matching against
342         * @param substring the substring to match at the given index
343         */
344        public static boolean substringMatch(CharSequence str, int index, CharSequence substring) {
345                if (index + substring.length() > str.length()) {
346                        return false;
347                }
348                for (int i = 0; i < substring.length(); i++) {
349                        if (str.charAt(index + i) != substring.charAt(i)) {
350                                return false;
351                        }
352                }
353                return true;
354        }
355
356        /**
357         * Count the occurrences of the substring {@code sub} in string {@code str}.
358         * @param str string to search in
359         * @param sub string to search for
360         */
361        public static int countOccurrencesOf(String str, String sub) {
362                if (!hasLength(str) || !hasLength(sub)) {
363                        return 0;
364                }
365
366                int count = 0;
367                int pos = 0;
368                int idx;
369                while ((idx = str.indexOf(sub, pos)) != -1) {
370                        ++count;
371                        pos = idx + sub.length();
372                }
373                return count;
374        }
375
376        /**
377         * Replace all occurrences of a substring within a string with another string.
378         * @param inString {@code String} to examine
379         * @param oldPattern {@code String} to replace
380         * @param newPattern {@code String} to insert
381         * @return a {@code String} with the replacements
382         */
383        public static String replace(String inString, String oldPattern, String newPattern) {
384                if (!hasLength(inString) || !hasLength(oldPattern) || newPattern == null) {
385                        return inString;
386                }
387                int index = inString.indexOf(oldPattern);
388                if (index == -1) {
389                        // no occurrence -> can return input as-is
390                        return inString;
391                }
392
393                int capacity = inString.length();
394                if (newPattern.length() > oldPattern.length()) {
395                        capacity += 16;
396                }
397                StringBuilder sb = new StringBuilder(capacity);
398
399                int pos = 0;  // our position in the old string
400                int patLen = oldPattern.length();
401                while (index >= 0) {
402                        sb.append(inString, pos, index);
403                        sb.append(newPattern);
404                        pos = index + patLen;
405                        index = inString.indexOf(oldPattern, pos);
406                }
407
408                // append any characters to the right of a match
409                sb.append(inString, pos, inString.length());
410                return sb.toString();
411        }
412
413        /**
414         * Delete all occurrences of the given substring.
415         * @param inString the original {@code String}
416         * @param pattern the pattern to delete all occurrences of
417         * @return the resulting {@code String}
418         */
419        public static String delete(String inString, String pattern) {
420                return replace(inString, pattern, "");
421        }
422
423        /**
424         * Delete any character in a given {@code String}.
425         * @param inString the original {@code String}
426         * @param charsToDelete a set of characters to delete.
427         * E.g. "az\n" will delete 'a's, 'z's and new lines.
428         * @return the resulting {@code String}
429         */
430        public static String deleteAny(String inString, String charsToDelete) {
431                if (!hasLength(inString) || !hasLength(charsToDelete)) {
432                        return inString;
433                }
434
435                StringBuilder sb = new StringBuilder(inString.length());
436                for (int i = 0; i < inString.length(); i++) {
437                        char c = inString.charAt(i);
438                        if (charsToDelete.indexOf(c) == -1) {
439                                sb.append(c);
440                        }
441                }
442                return sb.toString();
443        }
444
445
446        //---------------------------------------------------------------------
447        // Convenience methods for working with formatted Strings
448        //---------------------------------------------------------------------
449
450        /**
451         * Quote the given {@code String} with single quotes.
452         * @param str the input {@code String} (e.g. "myString")
453         * @return the quoted {@code String} (e.g. "'myString'"),
454         * or {@code null} if the input was {@code null}
455         */
456        public static String quote(String str) {
457                return (str != null ? "'" + str + "'" : null);
458        }
459
460        /**
461         * Turn the given Object into a {@code String} with single quotes
462         * if it is a {@code String}; keeping the Object as-is else.
463         * @param obj the input Object (e.g. "myString")
464         * @return the quoted {@code String} (e.g. "'myString'"),
465         * or the input object as-is if not a {@code String}
466         */
467        public static Object quoteIfString(Object obj) {
468                return (obj instanceof String ? quote((String) obj) : obj);
469        }
470
471        /**
472         * Unqualify a string qualified by a '.' dot character. For example,
473         * "this.name.is.qualified", returns "qualified".
474         * @param qualifiedName the qualified name
475         */
476        public static String unqualify(String qualifiedName) {
477                return unqualify(qualifiedName, '.');
478        }
479
480        /**
481         * Unqualify a string qualified by a separator character. For example,
482         * "this:name:is:qualified" returns "qualified" if using a ':' separator.
483         * @param qualifiedName the qualified name
484         * @param separator the separator
485         */
486        public static String unqualify(String qualifiedName, char separator) {
487                return qualifiedName.substring(qualifiedName.lastIndexOf(separator) + 1);
488        }
489
490        /**
491         * Capitalize a {@code String}, changing the first letter to
492         * upper case as per {@link Character#toUpperCase(char)}.
493         * No other letters are changed.
494         * @param str the {@code String} to capitalize
495         * @return the capitalized {@code String}
496         */
497        public static String capitalize(String str) {
498                return changeFirstCharacterCase(str, true);
499        }
500
501        /**
502         * Uncapitalize a {@code String}, changing the first letter to
503         * lower case as per {@link Character#toLowerCase(char)}.
504         * No other letters are changed.
505         * @param str the {@code String} to uncapitalize
506         * @return the uncapitalized {@code String}
507         */
508        public static String uncapitalize(String str) {
509                return changeFirstCharacterCase(str, false);
510        }
511
512        private static String changeFirstCharacterCase(String str, boolean capitalize) {
513                if (!hasLength(str)) {
514                        return str;
515                }
516
517                char baseChar = str.charAt(0);
518                char updatedChar;
519                if (capitalize) {
520                        updatedChar = Character.toUpperCase(baseChar);
521                }
522                else {
523                        updatedChar = Character.toLowerCase(baseChar);
524                }
525                if (baseChar == updatedChar) {
526                        return str;
527                }
528
529                char[] chars = str.toCharArray();
530                chars[0] = updatedChar;
531                return new String(chars, 0, chars.length);
532        }
533
534        /**
535         * Extract the filename from the given Java resource path,
536         * e.g. {@code "mypath/myfile.txt" -> "myfile.txt"}.
537         * @param path the file path (may be {@code null})
538         * @return the extracted filename, or {@code null} if none
539         */
540        public static String getFilename(String path) {
541                if (path == null) {
542                        return null;
543                }
544
545                int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR);
546                return (separatorIndex != -1 ? path.substring(separatorIndex + 1) : path);
547        }
548
549        /**
550         * Extract the filename extension from the given Java resource path,
551         * e.g. "mypath/myfile.txt" -> "txt".
552         * @param path the file path (may be {@code null})
553         * @return the extracted filename extension, or {@code null} if none
554         */
555        public static String getFilenameExtension(String path) {
556                if (path == null) {
557                        return null;
558                }
559
560                int extIndex = path.lastIndexOf(EXTENSION_SEPARATOR);
561                if (extIndex == -1) {
562                        return null;
563                }
564
565                int folderIndex = path.lastIndexOf(FOLDER_SEPARATOR);
566                if (folderIndex > extIndex) {
567                        return null;
568                }
569
570                return path.substring(extIndex + 1);
571        }
572
573        /**
574         * Strip the filename extension from the given Java resource path,
575         * e.g. "mypath/myfile.txt" -> "mypath/myfile".
576         * @param path the file path
577         * @return the path with stripped filename extension
578         */
579        public static String stripFilenameExtension(String path) {
580                if (path == null) {
581                        return null;
582                }
583
584                int extIndex = path.lastIndexOf(EXTENSION_SEPARATOR);
585                if (extIndex == -1) {
586                        return path;
587                }
588
589                int folderIndex = path.lastIndexOf(FOLDER_SEPARATOR);
590                if (folderIndex > extIndex) {
591                        return path;
592                }
593
594                return path.substring(0, extIndex);
595        }
596
597        /**
598         * Apply the given relative path to the given Java resource path,
599         * assuming standard Java folder separation (i.e. "/" separators).
600         * @param path the path to start from (usually a full file path)
601         * @param relativePath the relative path to apply
602         * (relative to the full file path above)
603         * @return the full file path that results from applying the relative path
604         */
605        public static String applyRelativePath(String path, String relativePath) {
606                int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR);
607                if (separatorIndex != -1) {
608                        String newPath = path.substring(0, separatorIndex);
609                        if (!relativePath.startsWith(FOLDER_SEPARATOR)) {
610                                newPath += FOLDER_SEPARATOR;
611                        }
612                        return newPath + relativePath;
613                }
614                else {
615                        return relativePath;
616                }
617        }
618
619        /**
620         * Normalize the path by suppressing sequences like "path/.." and
621         * inner simple dots.
622         * <p>The result is convenient for path comparison. For other uses,
623         * notice that Windows separators ("\") are replaced by simple slashes.
624         * @param path the original path
625         * @return the normalized path
626         */
627        public static String cleanPath(String path) {
628                if (path == null) {
629                        return null;
630                }
631                String pathToUse = replace(path, WINDOWS_FOLDER_SEPARATOR, FOLDER_SEPARATOR);
632
633                // Strip prefix from path to analyze, to not treat it as part of the
634                // first path element. This is necessary to correctly parse paths like
635                // "file:core/../core/io/Resource.class", where the ".." should just
636                // strip the first "core" directory while keeping the "file:" prefix.
637                int prefixIndex = pathToUse.indexOf(':');
638                String prefix = "";
639                if (prefixIndex != -1) {
640                        prefix = pathToUse.substring(0, prefixIndex + 1);
641                        if (prefix.contains(FOLDER_SEPARATOR)) {
642                                prefix = "";
643                        }
644                        else {
645                                pathToUse = pathToUse.substring(prefixIndex + 1);
646                        }
647                }
648                if (pathToUse.startsWith(FOLDER_SEPARATOR)) {
649                        prefix = prefix + FOLDER_SEPARATOR;
650                        pathToUse = pathToUse.substring(1);
651                }
652
653                String[] pathArray = delimitedListToStringArray(pathToUse, FOLDER_SEPARATOR);
654                List<String> pathElements = new LinkedList<String>();
655                int tops = 0;
656
657                for (int i = pathArray.length - 1; i >= 0; i--) {
658                        String element = pathArray[i];
659                        if (CURRENT_PATH.equals(element)) {
660                                // Points to current directory - drop it.
661                        }
662                        else if (TOP_PATH.equals(element)) {
663                                // Registering top path found.
664                                tops++;
665                        }
666                        else {
667                                if (tops > 0) {
668                                        // Merging path element with element corresponding to top path.
669                                        tops--;
670                                }
671                                else {
672                                        // Normal path element found.
673                                        pathElements.add(0, element);
674                                }
675                        }
676                }
677
678                // Remaining top paths need to be retained.
679                for (int i = 0; i < tops; i++) {
680                        pathElements.add(0, TOP_PATH);
681                }
682
683                return prefix + collectionToDelimitedString(pathElements, FOLDER_SEPARATOR);
684        }
685
686        /**
687         * Compare two paths after normalization of them.
688         * @param path1 first path for comparison
689         * @param path2 second path for comparison
690         * @return whether the two paths are equivalent after normalization
691         */
692        public static boolean pathEquals(String path1, String path2) {
693                return cleanPath(path1).equals(cleanPath(path2));
694        }
695
696        /**
697         * Parse the given {@code String} representation into a {@link Locale}.
698         * <p>This is the inverse operation of {@link Locale#toString Locale's toString}.
699         * @param localeString the locale {@code String}: following {@code Locale's}
700         * {@code toString()} format ("en", "en_UK", etc), also accepting spaces as
701         * separators (as an alternative to underscores)
702         * @return a corresponding {@code Locale} instance, or {@code null} if none
703         * @throws IllegalArgumentException in case of an invalid locale specification
704         */
705        public static Locale parseLocaleString(String localeString) {
706                String[] parts = tokenizeToStringArray(localeString, "_ ", false, false);
707                String language = (parts.length > 0 ? parts[0] : "");
708                String country = (parts.length > 1 ? parts[1] : "");
709
710                validateLocalePart(language);
711                validateLocalePart(country);
712
713                String variant = "";
714                if (parts.length > 2) {
715                        // There is definitely a variant, and it is everything after the country
716                        // code sans the separator between the country code and the variant.
717                        int endIndexOfCountryCode = localeString.indexOf(country, language.length()) + country.length();
718                        // Strip off any leading '_' and whitespace, what's left is the variant.
719                        variant = trimLeadingWhitespace(localeString.substring(endIndexOfCountryCode));
720                        if (variant.startsWith("_")) {
721                                variant = trimLeadingCharacter(variant, '_');
722                        }
723                }
724                return (language.length() > 0 ? new Locale(language, country, variant) : null);
725        }
726
727        private static void validateLocalePart(String localePart) {
728                for (int i = 0; i < localePart.length(); i++) {
729                        char ch = localePart.charAt(i);
730                        if (ch != ' ' && ch != '_' && ch != '#' && !Character.isLetterOrDigit(ch)) {
731                                throw new IllegalArgumentException(
732                                                "Locale part \"" + localePart + "\" contains invalid characters");
733                        }
734                }
735        }
736
737        /**
738         * Determine the RFC 3066 compliant language tag,
739         * as used for the HTTP "Accept-Language" header.
740         * @param locale the Locale to transform to a language tag
741         * @return the RFC 3066 compliant language tag as {@code String}
742         */
743        public static String toLanguageTag(Locale locale) {
744                return locale.getLanguage() + (hasText(locale.getCountry()) ? "-" + locale.getCountry() : "");
745        }
746
747        /**
748         * Parse the given {@code timeZoneString} value into a {@link TimeZone}.
749         * @param timeZoneString the time zone {@code String}, following {@link TimeZone#getTimeZone(String)}
750         * but throwing {@link IllegalArgumentException} in case of an invalid time zone specification
751         * @return a corresponding {@link TimeZone} instance
752         * @throws IllegalArgumentException in case of an invalid time zone specification
753         */
754        public static TimeZone parseTimeZoneString(String timeZoneString) {
755                TimeZone timeZone = TimeZone.getTimeZone(timeZoneString);
756                if ("GMT".equals(timeZone.getID()) && !timeZoneString.startsWith("GMT")) {
757                        // We don't want that GMT fallback...
758                        throw new IllegalArgumentException("Invalid time zone specification '" + timeZoneString + "'");
759                }
760                return timeZone;
761        }
762
763
764        //---------------------------------------------------------------------
765        // Convenience methods for working with String arrays
766        //---------------------------------------------------------------------
767
768        /**
769         * Copy the given {@link Collection} into a {@code String} array.
770         * <p>The {@code Collection} must contain {@code String} elements only.
771         * @param collection the {@code Collection} to copy
772         * @return the resulting {@code String} array
773         */
774        public static String[] toStringArray(Collection<String> collection) {
775                if (collection == null) {
776                        return null;
777                }
778                return collection.toArray(new String[collection.size()]);
779        }
780
781        /**
782         * Copy the given {@link Enumeration} into a {@code String} array.
783         * <p>The {@code Enumeration} must contain {@code String} elements only.
784         * @param enumeration the {@code Enumeration} to copy
785         * @return the resulting {@code String} array
786         */
787        public static String[] toStringArray(Enumeration<String> enumeration) {
788                if (enumeration == null) {
789                        return null;
790                }
791                return toStringArray(Collections.list(enumeration));
792        }
793
794        /**
795         * Append the given {@code String} to the given {@code String} array,
796         * returning a new array consisting of the input array contents plus
797         * the given {@code String}.
798         * @param array the array to append to (can be {@code null})
799         * @param str the {@code String} to append
800         * @return the new array (never {@code null})
801         */
802        public static String[] addStringToArray(String[] array, String str) {
803                if (ObjectUtils.isEmpty(array)) {
804                        return new String[] {str};
805                }
806
807                String[] newArr = new String[array.length + 1];
808                System.arraycopy(array, 0, newArr, 0, array.length);
809                newArr[array.length] = str;
810                return newArr;
811        }
812
813        /**
814         * Concatenate the given {@code String} arrays into one,
815         * with overlapping array elements included twice.
816         * <p>The order of elements in the original arrays is preserved.
817         * @param array1 the first array (can be {@code null})
818         * @param array2 the second array (can be {@code null})
819         * @return the new array ({@code null} if both given arrays were {@code null})
820         */
821        public static String[] concatenateStringArrays(String[] array1, String[] array2) {
822                if (ObjectUtils.isEmpty(array1)) {
823                        return array2;
824                }
825                if (ObjectUtils.isEmpty(array2)) {
826                        return array1;
827                }
828
829                String[] newArr = new String[array1.length + array2.length];
830                System.arraycopy(array1, 0, newArr, 0, array1.length);
831                System.arraycopy(array2, 0, newArr, array1.length, array2.length);
832                return newArr;
833        }
834
835        /**
836         * Merge the given {@code String} arrays into one, with overlapping
837         * array elements only included once.
838         * <p>The order of elements in the original arrays is preserved
839         * (with the exception of overlapping elements, which are only
840         * included on their first occurrence).
841         * @param array1 the first array (can be {@code null})
842         * @param array2 the second array (can be {@code null})
843         * @return the new array ({@code null} if both given arrays were {@code null})
844         * @deprecated as of 4.3.15, in favor of manual merging via {@link LinkedHashSet}
845         * (with every entry included at most once, even entries within the first array)
846         */
847        @Deprecated
848        public static String[] mergeStringArrays(String[] array1, String[] array2) {
849                if (ObjectUtils.isEmpty(array1)) {
850                        return array2;
851                }
852                if (ObjectUtils.isEmpty(array2)) {
853                        return array1;
854                }
855
856                List<String> result = new ArrayList<String>();
857                result.addAll(Arrays.asList(array1));
858                for (String str : array2) {
859                        if (!result.contains(str)) {
860                                result.add(str);
861                        }
862                }
863                return toStringArray(result);
864        }
865
866        /**
867         * Sort the given {@code String} array if necessary.
868         * @param array the original array
869         * @return the sorted array (never {@code null})
870         */
871        public static String[] sortStringArray(String[] array) {
872                if (ObjectUtils.isEmpty(array)) {
873                        return new String[0];
874                }
875
876                Arrays.sort(array);
877                return array;
878        }
879
880        /**
881         * Trim the elements of the given {@code String} array,
882         * calling {@code String.trim()} on each of them.
883         * @param array the original {@code String} array
884         * @return the resulting array (of the same size) with trimmed elements
885         */
886        public static String[] trimArrayElements(String[] array) {
887                if (ObjectUtils.isEmpty(array)) {
888                        return new String[0];
889                }
890
891                String[] result = new String[array.length];
892                for (int i = 0; i < array.length; i++) {
893                        String element = array[i];
894                        result[i] = (element != null ? element.trim() : null);
895                }
896                return result;
897        }
898
899        /**
900         * Remove duplicate strings from the given array.
901         * <p>As of 4.2, it preserves the original order, as it uses a {@link LinkedHashSet}.
902         * @param array the {@code String} array
903         * @return an array without duplicates, in natural sort order
904         */
905        public static String[] removeDuplicateStrings(String[] array) {
906                if (ObjectUtils.isEmpty(array)) {
907                        return array;
908                }
909
910                Set<String> set = new LinkedHashSet<String>();
911                for (String element : array) {
912                        set.add(element);
913                }
914                return toStringArray(set);
915        }
916
917        /**
918         * Split a {@code String} at the first occurrence of the delimiter.
919         * Does not include the delimiter in the result.
920         * @param toSplit the string to split
921         * @param delimiter to split the string up with
922         * @return a two element array with index 0 being before the delimiter, and
923         * index 1 being after the delimiter (neither element includes the delimiter);
924         * or {@code null} if the delimiter wasn't found in the given input {@code String}
925         */
926        public static String[] split(String toSplit, String delimiter) {
927                if (!hasLength(toSplit) || !hasLength(delimiter)) {
928                        return null;
929                }
930                int offset = toSplit.indexOf(delimiter);
931                if (offset < 0) {
932                        return null;
933                }
934
935                String beforeDelimiter = toSplit.substring(0, offset);
936                String afterDelimiter = toSplit.substring(offset + delimiter.length());
937                return new String[] {beforeDelimiter, afterDelimiter};
938        }
939
940        /**
941         * Take an array of strings and split each element based on the given delimiter.
942         * A {@code Properties} instance is then generated, with the left of the delimiter
943         * providing the key, and the right of the delimiter providing the value.
944         * <p>Will trim both the key and value before adding them to the {@code Properties}.
945         * @param array the array to process
946         * @param delimiter to split each element using (typically the equals symbol)
947         * @return a {@code Properties} instance representing the array contents,
948         * or {@code null} if the array to process was {@code null} or empty
949         */
950        public static Properties splitArrayElementsIntoProperties(String[] array, String delimiter) {
951                return splitArrayElementsIntoProperties(array, delimiter, null);
952        }
953
954        /**
955         * Take an array of strings and split each element based on the given delimiter.
956         * A {@code Properties} instance is then generated, with the left of the
957         * delimiter providing the key, and the right of the delimiter providing the value.
958         * <p>Will trim both the key and value before adding them to the
959         * {@code Properties} instance.
960         * @param array the array to process
961         * @param delimiter to split each element using (typically the equals symbol)
962         * @param charsToDelete one or more characters to remove from each element
963         * prior to attempting the split operation (typically the quotation mark
964         * symbol), or {@code null} if no removal should occur
965         * @return a {@code Properties} instance representing the array contents,
966         * or {@code null} if the array to process was {@code null} or empty
967         */
968        public static Properties splitArrayElementsIntoProperties(
969                        String[] array, String delimiter, String charsToDelete) {
970
971                if (ObjectUtils.isEmpty(array)) {
972                        return null;
973                }
974
975                Properties result = new Properties();
976                for (String element : array) {
977                        if (charsToDelete != null) {
978                                element = deleteAny(element, charsToDelete);
979                        }
980                        String[] splittedElement = split(element, delimiter);
981                        if (splittedElement == null) {
982                                continue;
983                        }
984                        result.setProperty(splittedElement[0].trim(), splittedElement[1].trim());
985                }
986                return result;
987        }
988
989        /**
990         * Tokenize the given {@code String} into a {@code String} array via a
991         * {@link StringTokenizer}.
992         * <p>Trims tokens and omits empty tokens.
993         * <p>The given {@code delimiters} string can consist of any number of
994         * delimiter characters. Each of those characters can be used to separate
995         * tokens. A delimiter is always a single character; for multi-character
996         * delimiters, consider using {@link #delimitedListToStringArray}.
997         * @param str the {@code String} to tokenize
998         * @param delimiters the delimiter characters, assembled as a {@code String}
999         * (each of the characters is individually considered as a delimiter)
1000         * @return an array of the tokens
1001         * @see java.util.StringTokenizer
1002         * @see String#trim()
1003         * @see #delimitedListToStringArray
1004         */
1005        public static String[] tokenizeToStringArray(String str, String delimiters) {
1006                return tokenizeToStringArray(str, delimiters, true, true);
1007        }
1008
1009        /**
1010         * Tokenize the given {@code String} into a {@code String} array via a
1011         * {@link StringTokenizer}.
1012         * <p>The given {@code delimiters} string can consist of any number of
1013         * delimiter characters. Each of those characters can be used to separate
1014         * tokens. A delimiter is always a single character; for multi-character
1015         * delimiters, consider using {@link #delimitedListToStringArray}.
1016         * @param str the {@code String} to tokenize
1017         * @param delimiters the delimiter characters, assembled as a {@code String}
1018         * (each of the characters is individually considered as a delimiter)
1019         * @param trimTokens trim the tokens via {@link String#trim()}
1020         * @param ignoreEmptyTokens omit empty tokens from the result array
1021         * (only applies to tokens that are empty after trimming; StringTokenizer
1022         * will not consider subsequent delimiters as token in the first place).
1023         * @return an array of the tokens
1024         * @see java.util.StringTokenizer
1025         * @see String#trim()
1026         * @see #delimitedListToStringArray
1027         */
1028        public static String[] tokenizeToStringArray(
1029                        String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) {
1030
1031                if (str == null) {
1032                        return null;
1033                }
1034
1035                StringTokenizer st = new StringTokenizer(str, delimiters);
1036                List<String> tokens = new ArrayList<String>();
1037                while (st.hasMoreTokens()) {
1038                        String token = st.nextToken();
1039                        if (trimTokens) {
1040                                token = token.trim();
1041                        }
1042                        if (!ignoreEmptyTokens || token.length() > 0) {
1043                                tokens.add(token);
1044                        }
1045                }
1046                return toStringArray(tokens);
1047        }
1048
1049        /**
1050         * Take a {@code String} that is a delimited list and convert it into a
1051         * {@code String} array.
1052         * <p>A single {@code delimiter} may consist of more than one character,
1053         * but it will still be considered as a single delimiter string, rather
1054         * than as bunch of potential delimiter characters, in contrast to
1055         * {@link #tokenizeToStringArray}.
1056         * @param str the input {@code String}
1057         * @param delimiter the delimiter between elements (this is a single delimiter,
1058         * rather than a bunch individual delimiter characters)
1059         * @return an array of the tokens in the list
1060         * @see #tokenizeToStringArray
1061         */
1062        public static String[] delimitedListToStringArray(String str, String delimiter) {
1063                return delimitedListToStringArray(str, delimiter, null);
1064        }
1065
1066        /**
1067         * Take a {@code String} that is a delimited list and convert it into
1068         * a {@code String} array.
1069         * <p>A single {@code delimiter} may consist of more than one character,
1070         * but it will still be considered as a single delimiter string, rather
1071         * than as bunch of potential delimiter characters, in contrast to
1072         * {@link #tokenizeToStringArray}.
1073         * @param str the input {@code String}
1074         * @param delimiter the delimiter between elements (this is a single delimiter,
1075         * rather than a bunch individual delimiter characters)
1076         * @param charsToDelete a set of characters to delete; useful for deleting unwanted
1077         * line breaks: e.g. "\r\n\f" will delete all new lines and line feeds in a {@code String}
1078         * @return an array of the tokens in the list
1079         * @see #tokenizeToStringArray
1080         */
1081        public static String[] delimitedListToStringArray(String str, String delimiter, String charsToDelete) {
1082                if (str == null) {
1083                        return new String[0];
1084                }
1085                if (delimiter == null) {
1086                        return new String[] {str};
1087                }
1088
1089                List<String> result = new ArrayList<String>();
1090                if ("".equals(delimiter)) {
1091                        for (int i = 0; i < str.length(); i++) {
1092                                result.add(deleteAny(str.substring(i, i + 1), charsToDelete));
1093                        }
1094                }
1095                else {
1096                        int pos = 0;
1097                        int delPos;
1098                        while ((delPos = str.indexOf(delimiter, pos)) != -1) {
1099                                result.add(deleteAny(str.substring(pos, delPos), charsToDelete));
1100                                pos = delPos + delimiter.length();
1101                        }
1102                        if (str.length() > 0 && pos <= str.length()) {
1103                                // Add rest of String, but not in case of empty input.
1104                                result.add(deleteAny(str.substring(pos), charsToDelete));
1105                        }
1106                }
1107                return toStringArray(result);
1108        }
1109
1110        /**
1111         * Convert a comma delimited list (e.g., a row from a CSV file) into an
1112         * array of strings.
1113         * @param str the input {@code String}
1114         * @return an array of strings, or the empty array in case of empty input
1115         */
1116        public static String[] commaDelimitedListToStringArray(String str) {
1117                return delimitedListToStringArray(str, ",");
1118        }
1119
1120        /**
1121         * Convert a comma delimited list (e.g., a row from a CSV file) into a set.
1122         * <p>Note that this will suppress duplicates, and as of 4.2, the elements in
1123         * the returned set will preserve the original order in a {@link LinkedHashSet}.
1124         * @param str the input {@code String}
1125         * @return a set of {@code String} entries in the list
1126         * @see #removeDuplicateStrings(String[])
1127         */
1128        public static Set<String> commaDelimitedListToSet(String str) {
1129                Set<String> set = new LinkedHashSet<String>();
1130                String[] tokens = commaDelimitedListToStringArray(str);
1131                for (String token : tokens) {
1132                        set.add(token);
1133                }
1134                return set;
1135        }
1136
1137        /**
1138         * Convert a {@link Collection} to a delimited {@code String} (e.g. CSV).
1139         * <p>Useful for {@code toString()} implementations.
1140         * @param coll the {@code Collection} to convert
1141         * @param delim the delimiter to use (typically a ",")
1142         * @param prefix the {@code String} to start each element with
1143         * @param suffix the {@code String} to end each element with
1144         * @return the delimited {@code String}
1145         */
1146        public static String collectionToDelimitedString(Collection<?> coll, String delim, String prefix, String suffix) {
1147                if (CollectionUtils.isEmpty(coll)) {
1148                        return "";
1149                }
1150
1151                StringBuilder sb = new StringBuilder();
1152                Iterator<?> it = coll.iterator();
1153                while (it.hasNext()) {
1154                        sb.append(prefix).append(it.next()).append(suffix);
1155                        if (it.hasNext()) {
1156                                sb.append(delim);
1157                        }
1158                }
1159                return sb.toString();
1160        }
1161
1162        /**
1163         * Convert a {@code Collection} into a delimited {@code String} (e.g. CSV).
1164         * <p>Useful for {@code toString()} implementations.
1165         * @param coll the {@code Collection} to convert
1166         * @param delim the delimiter to use (typically a ",")
1167         * @return the delimited {@code String}
1168         */
1169        public static String collectionToDelimitedString(Collection<?> coll, String delim) {
1170                return collectionToDelimitedString(coll, delim, "", "");
1171        }
1172
1173        /**
1174         * Convert a {@code Collection} into a delimited {@code String} (e.g., CSV).
1175         * <p>Useful for {@code toString()} implementations.
1176         * @param coll the {@code Collection} to convert
1177         * @return the delimited {@code String}
1178         */
1179        public static String collectionToCommaDelimitedString(Collection<?> coll) {
1180                return collectionToDelimitedString(coll, ",");
1181        }
1182
1183        /**
1184         * Convert a {@code String} array into a delimited {@code String} (e.g. CSV).
1185         * <p>Useful for {@code toString()} implementations.
1186         * @param arr the array to display
1187         * @param delim the delimiter to use (typically a ",")
1188         * @return the delimited {@code String}
1189         */
1190        public static String arrayToDelimitedString(Object[] arr, String delim) {
1191                if (ObjectUtils.isEmpty(arr)) {
1192                        return "";
1193                }
1194                if (arr.length == 1) {
1195                        return ObjectUtils.nullSafeToString(arr[0]);
1196                }
1197
1198                StringBuilder sb = new StringBuilder();
1199                for (int i = 0; i < arr.length; i++) {
1200                        if (i > 0) {
1201                                sb.append(delim);
1202                        }
1203                        sb.append(arr[i]);
1204                }
1205                return sb.toString();
1206        }
1207
1208        /**
1209         * Convert a {@code String} array into a comma delimited {@code String}
1210         * (i.e., CSV).
1211         * <p>Useful for {@code toString()} implementations.
1212         * @param arr the array to display
1213         * @return the delimited {@code String}
1214         */
1215        public static String arrayToCommaDelimitedString(Object[] arr) {
1216                return arrayToDelimitedString(arr, ",");
1217        }
1218
1219}