View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.shiro.util;
20  
21  import java.text.ParseException;
22  import java.util.*;
23  
24  /**
25   * <p>Simple utility class for String operations useful across the framework.
26   * <p/>
27   * <p>Some methods in this class were copied from the Spring Framework so we didn't have to re-invent the wheel,
28   * and in these cases, we have retained all license, copyright and author information.
29   *
30   * @since 0.9
31   */
32  public class StringUtils {
33  
34      //TODO - complete JavaDoc
35  
36      /**
37       * Constant representing the empty string, equal to &quot;&quot;
38       */
39      public static final String EMPTY_STRING = "";
40  
41      /**
42       * Constant representing the default delimiter character (comma), equal to <code>','</code>
43       */
44      public static final char DEFAULT_DELIMITER_CHAR = ',';
45  
46      /**
47       * Constant representing the default quote character (double quote), equal to '&quot;'</code>
48       */
49      public static final char DEFAULT_QUOTE_CHAR = '"';
50  
51      /**
52       * Check whether the given String has actual text.
53       * More specifically, returns <code>true</code> if the string not <code>null</code>,
54       * its length is greater than 0, and it contains at least one non-whitespace character.
55       * <p/>
56       * <code>StringUtils.hasText(null) == false<br/>
57       * StringUtils.hasText("") == false<br/>
58       * StringUtils.hasText(" ") == false<br/>
59       * StringUtils.hasText("12345") == true<br/>
60       * StringUtils.hasText(" 12345 ") == true</code>
61       * <p/>
62       * <p>Copied from the Spring Framework while retaining all license, copyright and author information.
63       *
64       * @param str the String to check (may be <code>null</code>)
65       * @return <code>true</code> if the String is not <code>null</code>, its length is
66       *         greater than 0, and it does not contain whitespace only
67       * @see java.lang.Character#isWhitespace
68       */
69      public static boolean hasText(String str) {
70          if (!hasLength(str)) {
71              return false;
72          }
73          int strLen = str.length();
74          for (int i = 0; i < strLen; i++) {
75              if (!Character.isWhitespace(str.charAt(i))) {
76                  return true;
77              }
78          }
79          return false;
80      }
81  
82      /**
83       * Check that the given String is neither <code>null</code> nor of length 0.
84       * Note: Will return <code>true</code> for a String that purely consists of whitespace.
85       * <p/>
86       * <code>StringUtils.hasLength(null) == false<br/>
87       * StringUtils.hasLength("") == false<br/>
88       * StringUtils.hasLength(" ") == true<br/>
89       * StringUtils.hasLength("Hello") == true</code>
90       * <p/>
91       * Copied from the Spring Framework while retaining all license, copyright and author information.
92       *
93       * @param str the String to check (may be <code>null</code>)
94       * @return <code>true</code> if the String is not null and has length
95       * @see #hasText(String)
96       */
97      public static boolean hasLength(String str) {
98          return (str != null && str.length() > 0);
99      }
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 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     //////////////////////////
503     // From CollectionUtils //
504     //////////////////////////
505     // CollectionUtils cannot be removed from shiro-core until 2.0 as it has a dependency on PrincipalCollection
506 
507 
508     private static <E> Set<E> asSet(E... elements) {
509         if (elements == null || elements.length == 0) {
510             return Collections.emptySet();
511         }
512 
513         if (elements.length == 1) {
514             return Collections.singleton(elements[0]);
515         }
516 
517         LinkedHashSet<E> set = new LinkedHashSet<E>(elements.length * 4 / 3 + 1);
518         Collections.addAll(set, elements);
519         return set;
520     }
521 
522 }