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 */
019package org.apache.shiro.util;
020
021import java.text.ParseException;
022import 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 */
032public 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}