001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    package org.apache.shiro.util;
020    
021    import java.text.ParseException;
022    import java.util.*;
023    
024    /**
025     * <p>Simple utility class for String operations useful across the framework.
026     * <p/>
027     * <p>Some methods in this class were copied from the Spring Framework so we didn't have to re-invent the wheel,
028     * and in these cases, we have retained all license, copyright and author information.
029     *
030     * @since 0.9
031     */
032    public class StringUtils {
033    
034        //TODO - complete JavaDoc
035    
036        /**
037         * Constant representing the empty string, equal to &quot;&quot;
038         */
039        public static final String EMPTY_STRING = "";
040    
041        /**
042         * Constant representing the default delimiter character (comma), equal to <code>','</code>
043         */
044        public static final char DEFAULT_DELIMITER_CHAR = ',';
045    
046        /**
047         * Constant representing the default quote character (double quote), equal to '&quot;'</code>
048         */
049        public static final char DEFAULT_QUOTE_CHAR = '"';
050    
051        /**
052         * Check whether the given String has actual text.
053         * More specifically, returns <code>true</code> if the string not <code>null</code>,
054         * its length is greater than 0, and it contains at least one non-whitespace character.
055         * <p/>
056         * <code>StringUtils.hasText(null) == false<br/>
057         * StringUtils.hasText("") == false<br/>
058         * StringUtils.hasText(" ") == false<br/>
059         * StringUtils.hasText("12345") == true<br/>
060         * StringUtils.hasText(" 12345 ") == true</code>
061         * <p/>
062         * <p>Copied from the Spring Framework while retaining all license, copyright and author information.
063         *
064         * @param str the String to check (may be <code>null</code>)
065         * @return <code>true</code> if the String is not <code>null</code>, its length is
066         *         greater than 0, and it does not contain whitespace only
067         * @see java.lang.Character#isWhitespace
068         */
069        public static boolean hasText(String str) {
070            if (!hasLength(str)) {
071                return false;
072            }
073            int strLen = str.length();
074            for (int i = 0; i < strLen; i++) {
075                if (!Character.isWhitespace(str.charAt(i))) {
076                    return true;
077                }
078            }
079            return false;
080        }
081    
082        /**
083         * Check that the given String is neither <code>null</code> nor of length 0.
084         * Note: Will return <code>true</code> for a String that purely consists of whitespace.
085         * <p/>
086         * <code>StringUtils.hasLength(null) == false<br/>
087         * StringUtils.hasLength("") == false<br/>
088         * StringUtils.hasLength(" ") == true<br/>
089         * StringUtils.hasLength("Hello") == true</code>
090         * <p/>
091         * Copied from the Spring Framework while retaining all license, copyright and author information.
092         *
093         * @param str the String to check (may be <code>null</code>)
094         * @return <code>true</code> if the String is not null and has length
095         * @see #hasText(String)
096         */
097        public static boolean hasLength(String str) {
098            return (str != null && str.length() > 0);
099        }
100    
101    
102        /**
103         * Test if the given String starts with the specified prefix,
104         * ignoring upper/lower case.
105         * <p/>
106         * <p>Copied from the Spring Framework while retaining all license, copyright and author information.
107         *
108         * @param str    the String to check
109         * @param prefix the prefix to look for
110         * @return <code>true</code> starts with the specified prefix (ignoring case), <code>false</code> if it does not.
111         * @see java.lang.String#startsWith
112         */
113        public static boolean startsWithIgnoreCase(String str, String prefix) {
114            if (str == null || prefix == null) {
115                return false;
116            }
117            if (str.startsWith(prefix)) {
118                return true;
119            }
120            if (str.length() < prefix.length()) {
121                return false;
122            }
123            String lcStr = str.substring(0, prefix.length()).toLowerCase();
124            String lcPrefix = prefix.toLowerCase();
125            return lcStr.equals(lcPrefix);
126        }
127    
128        /**
129         * Returns a 'cleaned' representation of the specified argument.  'Cleaned' is defined as the following:
130         * <p/>
131         * <ol>
132         * <li>If the specified <code>String</code> is <code>null</code>, return <code>null</code></li>
133         * <li>If not <code>null</code>, {@link String#trim() trim()} it.</li>
134         * <li>If the trimmed string is equal to the empty String (i.e. &quot;&quot;), return <code>null</code></li>
135         * <li>If the trimmed string is not the empty string, return the trimmed version</li>.
136         * </ol>
137         * <p/>
138         * Therefore this method always ensures that any given string has trimmed text, and if it doesn't, <code>null</code>
139         * is returned.
140         *
141         * @param in the input String to clean.
142         * @return a populated-but-trimmed String or <code>null</code> otherwise
143         */
144        public static String clean(String in) {
145            String out = in;
146    
147            if (in != null) {
148                out = in.trim();
149                if (out.equals(EMPTY_STRING)) {
150                    out = null;
151                }
152            }
153    
154            return out;
155        }
156    
157        /**
158         * Returns the specified array as a comma-delimited (',') string.
159         *
160         * @param array the array whose contents will be converted to a string.
161         * @return the array's contents as a comma-delimited (',') string.
162         * @since 1.0
163         */
164        public static String toString(Object[] array) {
165            return toDelimitedString(array, ",");
166        }
167    
168        /**
169         * Returns the array's contents as a string, with each element delimited by the specified
170         * {@code delimiter} argument.  Useful for {@code toString()} implementations and log messages.
171         *
172         * @param array     the array whose contents will be converted to a string
173         * @param delimiter the delimiter to use between each element
174         * @return a single string, delimited by the specified {@code delimiter}.
175         * @since 1.0
176         */
177        public static String toDelimitedString(Object[] array, String delimiter) {
178            if (array == null || array.length == 0) {
179                return EMPTY_STRING;
180            }
181            StringBuilder sb = new StringBuilder();
182            for (int i = 0; i < array.length; i++) {
183                if (i > 0) {
184                    sb.append(delimiter);
185                }
186                sb.append(array[i]);
187            }
188            return sb.toString();
189        }
190    
191        /**
192         * Returns the collection's contents as a string, with each element delimited by the specified
193         * {@code delimiter} argument.  Useful for {@code toString()} implementations and log messages.
194         *
195         * @param c         the collection whose contents will be converted to a string
196         * @param delimiter the delimiter to use between each element
197         * @return a single string, delimited by the specified {@code delimiter}.
198         * @since 1.2
199         */
200        public static String toDelimitedString(Collection c, String delimiter) {
201            if (c == null || c.isEmpty()) {
202                return EMPTY_STRING;
203            }
204            return join(c.iterator(), delimiter);
205        }
206    
207        /**
208         * Tokenize the given String into a String array via a StringTokenizer.
209         * Trims tokens and omits empty tokens.
210         * <p>The given delimiters string is supposed to consist of any number of
211         * delimiter characters. Each of those characters can be used to separate
212         * tokens. A delimiter is always a single character; for multi-character
213         * delimiters, consider using <code>delimitedListToStringArray</code>
214         * <p/>
215         * <p>Copied from the Spring Framework while retaining all license, copyright and author information.
216         *
217         * @param str        the String to tokenize
218         * @param delimiters the delimiter characters, assembled as String
219         *                   (each of those characters is individually considered as delimiter).
220         * @return an array of the tokens
221         * @see java.util.StringTokenizer
222         * @see java.lang.String#trim()
223         */
224        public static String[] tokenizeToStringArray(String str, String delimiters) {
225            return tokenizeToStringArray(str, delimiters, true, true);
226        }
227    
228        /**
229         * Tokenize the given String into a String array via a StringTokenizer.
230         * <p>The given delimiters string is supposed to consist of any number of
231         * delimiter characters. Each of those characters can be used to separate
232         * tokens. A delimiter is always a single character; for multi-character
233         * delimiters, consider using <code>delimitedListToStringArray</code>
234         * <p/>
235         * <p>Copied from the Spring Framework while retaining all license, copyright and author information.
236         *
237         * @param str               the String to tokenize
238         * @param delimiters        the delimiter characters, assembled as String
239         *                          (each of those characters is individually considered as delimiter)
240         * @param trimTokens        trim the tokens via String's <code>trim</code>
241         * @param ignoreEmptyTokens omit empty tokens from the result array
242         *                          (only applies to tokens that are empty after trimming; StringTokenizer
243         *                          will not consider subsequent delimiters as token in the first place).
244         * @return an array of the tokens (<code>null</code> if the input String
245         *         was <code>null</code>)
246         * @see java.util.StringTokenizer
247         * @see java.lang.String#trim()
248         */
249        @SuppressWarnings({"unchecked"})
250        public static String[] tokenizeToStringArray(
251                String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) {
252    
253            if (str == null) {
254                return null;
255            }
256            StringTokenizer st = new StringTokenizer(str, delimiters);
257            List tokens = new ArrayList();
258            while (st.hasMoreTokens()) {
259                String token = st.nextToken();
260                if (trimTokens) {
261                    token = token.trim();
262                }
263                if (!ignoreEmptyTokens || token.length() > 0) {
264                    tokens.add(token);
265                }
266            }
267            return toStringArray(tokens);
268        }
269    
270        /**
271         * Copy the given Collection into a String array.
272         * The Collection must contain String elements only.
273         * <p/>
274         * <p>Copied from the Spring Framework while retaining all license, copyright and author information.
275         *
276         * @param collection the Collection to copy
277         * @return the String array (<code>null</code> if the passed-in
278         *         Collection was <code>null</code>)
279         */
280        @SuppressWarnings({"unchecked"})
281        public static String[] toStringArray(Collection collection) {
282            if (collection == null) {
283                return null;
284            }
285            return (String[]) collection.toArray(new String[collection.size()]);
286        }
287    
288        public static String[] splitKeyValue(String aLine) throws ParseException {
289            String line = clean(aLine);
290            if (line == null) {
291                return null;
292            }
293            String[] split = line.split(" ", 2);
294            if (split.length != 2) {
295                //fallback to checking for an equals sign
296                split = line.split("=", 2);
297                if (split.length != 2) {
298                    String msg = "Unable to determine Key/Value pair from line [" + line + "].  There is no space from " +
299                            "which the split location could be determined.";
300                    throw new ParseException(msg, 0);
301                }
302    
303            }
304    
305            split[0] = clean(split[0]);
306            split[1] = clean(split[1]);
307            if (split[1].startsWith("=")) {
308                //they used spaces followed by an equals followed by zero or more spaces to split the key/value pair, so
309                //remove the equals sign to result in only the key and values in the
310                split[1] = clean(split[1].substring(1));
311            }
312    
313            if (split[0] == null) {
314                String msg = "No valid key could be found in line [" + line + "] to form a key/value pair.";
315                throw new ParseException(msg, 0);
316            }
317            if (split[1] == null) {
318                String msg = "No corresponding value could be found in line [" + line + "] for key [" + split[0] + "]";
319                throw new ParseException(msg, 0);
320            }
321    
322            return split;
323        }
324    
325        public static String[] split(String line) {
326            return split(line, DEFAULT_DELIMITER_CHAR);
327        }
328    
329        public static String[] split(String line, char delimiter) {
330            return split(line, delimiter, DEFAULT_QUOTE_CHAR);
331        }
332    
333        public static String[] split(String line, char delimiter, char quoteChar) {
334            return split(line, delimiter, quoteChar, quoteChar);
335        }
336    
337        public static String[] split(String line, char delimiter, char beginQuoteChar, char endQuoteChar) {
338            return split(line, delimiter, beginQuoteChar, endQuoteChar, false, true);
339        }
340    
341        /**
342         * Splits the specified delimited String into tokens, supporting quoted tokens so that quoted strings themselves
343         * won't be tokenized.
344         * <p/>
345         * This method's implementation is very loosely based (with significant modifications) on
346         * <a href="http://blogs.bytecode.com.au/glen">Glen Smith</a>'s open-source
347         * <a href="http://opencsv.svn.sourceforge.net/viewvc/opencsv/trunk/src/au/com/bytecode/opencsv/CSVReader.java?&view=markup">CSVReader.java</a>
348         * file.
349         * <p/>
350         * That file is Apache 2.0 licensed as well, making Glen's code a great starting point for us to modify to
351         * our needs.
352         *
353         * @param aLine          the String to parse
354         * @param delimiter      the delimiter by which the <tt>line</tt> argument is to be split
355         * @param beginQuoteChar the character signifying the start of quoted text (so the quoted text will not be split)
356         * @param endQuoteChar   the character signifying the end of quoted text
357         * @param retainQuotes   if the quotes themselves should be retained when constructing the corresponding token
358         * @param trimTokens     if leading and trailing whitespace should be trimmed from discovered tokens.
359         * @return the tokens discovered from parsing the given delimited <tt>line</tt>.
360         */
361        public static String[] split(String aLine, char delimiter, char beginQuoteChar, char endQuoteChar,
362                                     boolean retainQuotes, boolean trimTokens) {
363            String line = clean(aLine);
364            if (line == null) {
365                return null;
366            }
367    
368            List<String> tokens = new ArrayList<String>();
369            StringBuilder sb = new StringBuilder();
370            boolean inQuotes = false;
371    
372            for (int i = 0; i < line.length(); i++) {
373    
374                char c = line.charAt(i);
375                if (c == beginQuoteChar) {
376                    // this gets complex... the quote may end a quoted block, or escape another quote.
377                    // do a 1-char lookahead:
378                    if (inQuotes  // we are in quotes, therefore there can be escaped quotes in here.
379                            && line.length() > (i + 1)  // there is indeed another character to check.
380                            && line.charAt(i + 1) == beginQuoteChar) { // ..and that char. is a quote also.
381                        // we have two quote chars in a row == one quote char, so consume them both and
382                        // put one on the token. we do *not* exit the quoted text.
383                        sb.append(line.charAt(i + 1));
384                        i++;
385                    } else {
386                        inQuotes = !inQuotes;
387                        if (retainQuotes) {
388                            sb.append(c);
389                        }
390                    }
391                } else if (c == endQuoteChar) {
392                    inQuotes = !inQuotes;
393                    if (retainQuotes) {
394                        sb.append(c);
395                    }
396                } else if (c == delimiter && !inQuotes) {
397                    String s = sb.toString();
398                    if (trimTokens) {
399                        s = s.trim();
400                    }
401                    tokens.add(s);
402                    sb = new StringBuilder(); // start work on next token
403                } else {
404                    sb.append(c);
405                }
406            }
407            String s = sb.toString();
408            if (trimTokens) {
409                s = s.trim();
410            }
411            tokens.add(s);
412            return tokens.toArray(new String[tokens.size()]);
413        }
414    
415        /**
416         * Joins the elements of the provided {@code Iterator} into
417         * a single String containing the provided elements.</p>
418         * <p/>
419         * No delimiter is added before or after the list.
420         * A {@code null} separator is the same as an empty String ("").</p>
421         * <p/>
422         * Copied from Commons Lang, version 3 (r1138702).</p>
423         *
424         * @param iterator  the {@code Iterator} of values to join together, may be null
425         * @param separator the separator character to use, null treated as ""
426         * @return the joined String, {@code null} if null iterator input
427         * @since 1.2
428         */
429        public static String join(Iterator<?> iterator, String separator) {
430            final String empty = "";
431    
432            // handle null, zero and one elements before building a buffer
433            if (iterator == null) {
434                return null;
435            }
436            if (!iterator.hasNext()) {
437                return empty;
438            }
439            Object first = iterator.next();
440            if (!iterator.hasNext()) {
441                return first == null ? empty : first.toString();
442            }
443    
444            // two or more elements
445            StringBuilder buf = new StringBuilder(256); // Java default is 16, probably too small
446            if (first != null) {
447                buf.append(first);
448            }
449    
450            while (iterator.hasNext()) {
451                if (separator != null) {
452                    buf.append(separator);
453                }
454                Object obj = iterator.next();
455                if (obj != null) {
456                    buf.append(obj);
457                }
458            }
459            return buf.toString();
460        }
461    
462        /**
463         * Splits the {@code delimited} string (delimited by the specified {@code separator} character) and returns the
464         * delimited values as a {@code Set}.
465         * <p/>
466         * If either argument is {@code null}, this method returns {@code null}.
467         *
468         * @param delimited the string to split
469         * @param separator the character that delineates individual tokens to split
470         * @return the delimited values as a {@code Set}.
471         * @since 1.2
472         */
473        public static Set<String> splitToSet(String delimited, String separator) {
474            if (delimited == null || separator == null) {
475                return null;
476            }
477            String[] split = split(delimited, separator.charAt(0));
478            return CollectionUtils.asSet(split);
479        }
480    
481        /**
482         * Returns the input argument, but ensures the first character is capitalized (if possible).
483         * @param in the string to uppercase the first character.
484         * @return the input argument, but with the first character capitalized (if possible).
485         * @since 1.2
486         */
487        public static String uppercaseFirstChar(String in) {
488            if (in == null || in.length() == 0) {
489                return in;
490            }
491            int length = in.length();
492            StringBuilder sb = new StringBuilder(length);
493    
494            sb.append(Character.toUpperCase(in.charAt(0)));
495            if (length > 1) {
496                String remaining = in.substring(1);
497                sb.append(remaining);
498            }
499            return sb.toString();
500        }
501    
502    }