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 "" 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 '"'</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. ""), 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 }