001/* 002 * ModeShape (http://www.modeshape.org) 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 * http://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 */ 016package org.modeshape.common.util; 017 018import java.io.ByteArrayOutputStream; 019import java.io.IOException; 020import java.io.InputStream; 021import java.io.OutputStream; 022import java.io.PrintWriter; 023import java.io.Reader; 024import java.io.UnsupportedEncodingException; 025import java.io.Writer; 026import java.lang.reflect.Array; 027import java.math.BigInteger; 028import java.text.CharacterIterator; 029import java.text.StringCharacterIterator; 030import java.util.ArrayList; 031import java.util.Arrays; 032import java.util.Collections; 033import java.util.List; 034import java.util.regex.Matcher; 035import java.util.regex.Pattern; 036import org.modeshape.common.CommonI18n; 037import org.modeshape.common.annotation.Immutable; 038 039/** 040 * Utilities for string processing and manipulation. 041 */ 042@Immutable 043public class StringUtil { 044 045 public static final String[] EMPTY_STRING_ARRAY = new String[0]; 046 private static final Pattern NORMALIZE_PATTERN = Pattern.compile("\\s+"); 047 private static final Pattern PARAMETER_COUNT_PATTERN = Pattern.compile("\\{(\\d+)\\}"); 048 049 /** 050 * Combine the lines into a single string, using the new line character as the delimiter. This is compatible with 051 * {@link #splitLines(String)}. 052 * 053 * @param lines the lines to be combined 054 * @return the combined lines, or an empty string if there are no lines 055 */ 056 public static String combineLines( String[] lines ) { 057 return combineLines(lines, '\n'); 058 } 059 060 /** 061 * Combine the lines into a single string, using the supplied separator as the delimiter. 062 * 063 * @param lines the lines to be combined 064 * @param separator the separator character 065 * @return the combined lines, or an empty string if there are no lines 066 */ 067 public static String combineLines( String[] lines, 068 char separator ) { 069 if (lines == null || lines.length == 0) return ""; 070 StringBuilder sb = new StringBuilder(); 071 for (int i = 0; i != lines.length; ++i) { 072 String line = lines[i]; 073 if (i != 0) sb.append(separator); 074 sb.append(line); 075 } 076 return sb.toString(); 077 } 078 079 /** 080 * Split the supplied content into lines, returning each line as an element in the returned list. 081 * 082 * @param content the string content that is to be split 083 * @return the list of lines; never null but may be an empty (unmodifiable) list if the supplied content is null or empty 084 */ 085 public static List<String> splitLines( final String content ) { 086 if (content == null || content.length() == 0) return Collections.emptyList(); 087 String[] lines = content.split("[\\r]?\\n"); 088 return Arrays.asList(lines); 089 } 090 091 /** 092 * Combine the supplied values into a single string, using the supplied string to delimit values. 093 * 094 * @param values the values to be combined; may not be null, but may contain null values (which are skipped) 095 * @param delimiter the characters to place between each of the values; may not be null 096 * @return the joined string; never null 097 * @see String#split(String) 098 */ 099 public static String join( Object[] values, 100 String delimiter ) { 101 StringBuilder sb = new StringBuilder(); 102 boolean first = true; 103 for (Object segment : values) { 104 if (segment == null) continue; 105 if (first) first = false; 106 else sb.append(delimiter); 107 sb.append(segment); 108 } 109 return sb.toString(); 110 } 111 112 /** 113 * Combine the supplied values into a single string, using the supplied string to delimit values. 114 * 115 * @param values the values to be combined; may not be null, but may contain null values (which are skipped) 116 * @param delimiter the characters to place between each of the values; may not be null 117 * @return the joined string; never null 118 * @see String#split(String) 119 */ 120 public static String join( Iterable<?> values, 121 String delimiter ) { 122 StringBuilder sb = new StringBuilder(); 123 boolean first = true; 124 for (Object segment : values) { 125 if (segment == null) continue; 126 if (first) first = false; 127 else sb.append(delimiter); 128 sb.append(segment); 129 } 130 return sb.toString(); 131 } 132 133 /** 134 * Create a string by substituting the parameters into all key occurrences in the supplied format. The pattern consists of 135 * zero or more keys of the form <code>{n}</code>, where <code>n</code> is an integer starting at 0. Therefore, the first 136 * parameter replaces all occurrences of "{0}", the second parameter replaces all occurrences of "{1}", etc. 137 * <p> 138 * If any parameter is null, the corresponding key is replaced with the string "null". Therefore, consider using an empty 139 * string when keys are to be removed altogether. 140 * </p> 141 * <p> 142 * If there are no parameters, this method does nothing and returns the supplied pattern as is. 143 * </p> 144 * 145 * @param pattern the pattern 146 * @param parameters the parameters used to replace keys 147 * @return the string with all keys replaced (or removed) 148 */ 149 public static String createString( String pattern, 150 Object... parameters ) { 151 CheckArg.isNotNull(pattern, "pattern"); 152 if (parameters == null) parameters = EMPTY_STRING_ARRAY; 153 Matcher matcher = PARAMETER_COUNT_PATTERN.matcher(pattern); 154 // CHECKSTYLE IGNORE check FOR NEXT 1 LINES 155 StringBuffer text = new StringBuffer(); 156 int requiredParameterCount = 0; 157 boolean err = false; 158 while (matcher.find()) { 159 int ndx = Integer.valueOf(matcher.group(1)); 160 if (requiredParameterCount <= ndx) { 161 requiredParameterCount = ndx + 1; 162 } 163 if (ndx >= parameters.length) { 164 err = true; 165 matcher.appendReplacement(text, matcher.group()); 166 } else { 167 Object parameter = parameters[ndx]; 168 169 // Automatically pretty-print arrays 170 if (parameter != null && parameter.getClass().isArray()) { 171 if (parameter instanceof Object[]) { 172 parameter = Arrays.asList((Object[])parameter); 173 } else { 174 int length = Array.getLength(parameter); 175 List<Object> parameterAsList = new ArrayList<Object>(length); 176 for (int i = 0; i < length; i++) { 177 parameterAsList.add(Array.get(parameter, i)); 178 } 179 parameter = parameterAsList; 180 } 181 } 182 183 matcher.appendReplacement(text, Matcher.quoteReplacement(parameter == null ? "null" : parameter.toString())); 184 } 185 } 186 if (err || requiredParameterCount < parameters.length) { 187 throw new IllegalArgumentException( 188 CommonI18n.requiredToSuppliedParameterMismatch.text(parameters.length, 189 parameters.length == 1 ? "" : "s", 190 requiredParameterCount, 191 requiredParameterCount == 1 ? "" : "s", 192 pattern, text.toString())); 193 } 194 matcher.appendTail(text); 195 196 return text.toString(); 197 } 198 199 /** 200 * Create a new string containing the specified character repeated a specific number of times. 201 * 202 * @param charToRepeat the character to repeat 203 * @param numberOfRepeats the number of times the character is to repeat in the result; must be greater than 0 204 * @return the resulting string 205 */ 206 public static String createString( final char charToRepeat, 207 int numberOfRepeats ) { 208 assert numberOfRepeats >= 0; 209 StringBuilder sb = new StringBuilder(); 210 for (int i = 0; i < numberOfRepeats; ++i) { 211 sb.append(charToRepeat); 212 } 213 return sb.toString(); 214 } 215 216 /** 217 * Set the length of the string, padding with the supplied character if the supplied string is shorter than desired, or 218 * truncating the string if it is longer than desired. Unlike {@link #justifyLeft(String, int, char)}, this method does not 219 * remove leading and trailing whitespace. 220 * 221 * @param original the string for which the length is to be set; may not be null 222 * @param length the desired length; must be positive 223 * @param padChar the character to use for padding, if the supplied string is not long enough 224 * @return the string of the desired length 225 * @see #justifyLeft(String, int, char) 226 */ 227 public static String setLength( String original, 228 int length, 229 char padChar ) { 230 return justifyLeft(original, length, padChar, false); 231 } 232 233 public static enum Justify { 234 LEFT, 235 RIGHT, 236 CENTER; 237 } 238 239 /** 240 * Justify the contents of the string. 241 * 242 * @param justify the way in which the string is to be justified 243 * @param str the string to be right justified; if null, an empty string is used 244 * @param width the desired width of the string; must be positive 245 * @param padWithChar the character to use for padding, if needed 246 * @return the right justified string 247 */ 248 public static String justify( Justify justify, 249 String str, 250 final int width, 251 char padWithChar ) { 252 switch (justify) { 253 case LEFT: 254 return justifyLeft(str, width, padWithChar); 255 case RIGHT: 256 return justifyRight(str, width, padWithChar); 257 case CENTER: 258 return justifyCenter(str, width, padWithChar); 259 } 260 assert false; 261 return null; 262 } 263 264 /** 265 * Right justify the contents of the string, ensuring that the string ends at the last character. If the supplied string is 266 * longer than the desired width, the leading characters are removed so that the last character in the supplied string at the 267 * last position. If the supplied string is shorter than the desired width, the padding character is inserted one or more 268 * times such that the last character in the supplied string appears as the last character in the resulting string and that 269 * the length matches that specified. 270 * 271 * @param str the string to be right justified; if null, an empty string is used 272 * @param width the desired width of the string; must be positive 273 * @param padWithChar the character to use for padding, if needed 274 * @return the right justified string 275 */ 276 public static String justifyRight( String str, 277 final int width, 278 char padWithChar ) { 279 assert width > 0; 280 // Trim the leading and trailing whitespace ... 281 str = str != null ? str.trim() : ""; 282 283 final int length = str.length(); 284 int addChars = width - length; 285 if (addChars < 0) { 286 // truncate the first characters, keep the last 287 return str.subSequence(length - width, length).toString(); 288 } 289 // Prepend the whitespace ... 290 final StringBuilder sb = new StringBuilder(); 291 while (addChars > 0) { 292 sb.append(padWithChar); 293 --addChars; 294 } 295 296 // Write the content ... 297 sb.append(str); 298 return sb.toString(); 299 } 300 301 /** 302 * Left justify the contents of the string, ensuring that the supplied string begins at the first character and that the 303 * resulting string is of the desired length. If the supplied string is longer than the desired width, it is truncated to the 304 * specified length. If the supplied string is shorter than the desired width, the padding character is added to the end of 305 * the string one or more times such that the length is that specified. All leading and trailing whitespace is removed. 306 * 307 * @param str the string to be left justified; if null, an empty string is used 308 * @param width the desired width of the string; must be positive 309 * @param padWithChar the character to use for padding, if needed 310 * @return the left justified string 311 * @see #setLength(String, int, char) 312 */ 313 public static String justifyLeft( String str, 314 final int width, 315 char padWithChar ) { 316 return justifyLeft(str, width, padWithChar, true); 317 } 318 319 protected static String justifyLeft( String str, 320 final int width, 321 char padWithChar, 322 boolean trimWhitespace ) { 323 // Trim the leading and trailing whitespace ... 324 str = str != null ? (trimWhitespace ? str.trim() : str) : ""; 325 326 int addChars = width - str.length(); 327 if (addChars < 0) { 328 // truncate 329 return str.subSequence(0, width).toString(); 330 } 331 // Write the content ... 332 final StringBuilder sb = new StringBuilder(); 333 sb.append(str); 334 335 // Append the whitespace ... 336 while (addChars > 0) { 337 sb.append(padWithChar); 338 --addChars; 339 } 340 341 return sb.toString(); 342 } 343 344 /** 345 * Center the contents of the string. If the supplied string is longer than the desired width, it is truncated to the 346 * specified length. If the supplied string is shorter than the desired width, padding characters are added to the beginning 347 * and end of the string such that the length is that specified; one additional padding character is prepended if required. 348 * All leading and trailing whitespace is removed before centering. 349 * 350 * @param str the string to be left justified; if null, an empty string is used 351 * @param width the desired width of the string; must be positive 352 * @param padWithChar the character to use for padding, if needed 353 * @return the left justified string 354 * @see #setLength(String, int, char) 355 */ 356 public static String justifyCenter( String str, 357 final int width, 358 char padWithChar ) { 359 // Trim the leading and trailing whitespace ... 360 str = str != null ? str.trim() : ""; 361 362 int addChars = width - str.length(); 363 if (addChars < 0) { 364 // truncate 365 return str.subSequence(0, width).toString(); 366 } 367 // Write the content ... 368 int prependNumber = addChars / 2; 369 int appendNumber = prependNumber; 370 if ((prependNumber + appendNumber) != addChars) { 371 ++prependNumber; 372 } 373 374 final StringBuilder sb = new StringBuilder(); 375 376 // Prepend the pad character(s) ... 377 while (prependNumber > 0) { 378 sb.append(padWithChar); 379 --prependNumber; 380 } 381 382 // Add the actual content 383 sb.append(str); 384 385 // Append the pad character(s) ... 386 while (appendNumber > 0) { 387 sb.append(padWithChar); 388 --appendNumber; 389 } 390 391 return sb.toString(); 392 } 393 394 /** 395 * Truncate the supplied string to be no more than the specified length. This method returns an empty string if the supplied 396 * object is null. 397 * 398 * @param obj the object from which the string is to be obtained using {@link Object#toString()}. 399 * @param maxLength the maximum length of the string being returned 400 * @return the supplied string if no longer than the maximum length, or the supplied string truncated to be no longer than the 401 * maximum length (including the suffix) 402 * @throws IllegalArgumentException if the maximum length is negative 403 */ 404 public static String truncate( Object obj, 405 int maxLength ) { 406 return truncate(obj, maxLength, null); 407 } 408 409 /** 410 * Truncate the supplied string to be no more than the specified length. This method returns an empty string if the supplied 411 * object is null. 412 * 413 * @param obj the object from which the string is to be obtained using {@link Object#toString()}. 414 * @param maxLength the maximum length of the string being returned 415 * @param suffix the suffix that should be added to the content if the string must be truncated, or null if the default suffix 416 * of "..." should be used 417 * @return the supplied string if no longer than the maximum length, or the supplied string truncated to be no longer than the 418 * maximum length (including the suffix) 419 * @throws IllegalArgumentException if the maximum length is negative 420 */ 421 public static String truncate( Object obj, 422 int maxLength, 423 String suffix ) { 424 CheckArg.isNonNegative(maxLength, "maxLength"); 425 if (obj == null || maxLength == 0) { 426 return ""; 427 } 428 String str = obj.toString(); 429 if (str.length() <= maxLength) return str; 430 if (suffix == null) suffix = "..."; 431 int maxNumChars = maxLength - suffix.length(); 432 if (maxNumChars < 0) { 433 // Then the max length is actually shorter than the suffix ... 434 str = suffix.substring(0, maxLength); 435 } else if (str.length() > maxNumChars) { 436 str = str.substring(0, maxNumChars) + suffix; 437 } 438 return str; 439 } 440 441 /** 442 * Read and return the entire contents of the supplied {@link Reader}. This method always closes the reader when finished 443 * reading. 444 * 445 * @param reader the reader of the contents; may be null 446 * @return the contents, or an empty string if the supplied reader is null 447 * @throws IOException if there is an error reading the content 448 */ 449 public static String read( Reader reader ) throws IOException { 450 return IoUtil.read(reader); 451 } 452 453 /** 454 * Read and return the entire contents of the supplied {@link InputStream}. This method always closes the stream when finished 455 * reading. 456 * 457 * @param stream the streamed contents; may be null 458 * @return the contents, or an empty string if the supplied stream is null 459 * @throws IOException if there is an error reading the content 460 */ 461 public static String read( InputStream stream ) throws IOException { 462 return IoUtil.read(stream); 463 } 464 465 /** 466 * Write the entire contents of the supplied string to the given stream. This method always flushes and closes the stream when 467 * finished. 468 * 469 * @param content the content to write to the stream; may be null 470 * @param stream the stream to which the content is to be written 471 * @throws IOException 472 * @throws IllegalArgumentException if the stream is null 473 */ 474 public static void write( String content, 475 OutputStream stream ) throws IOException { 476 IoUtil.write(content, stream); 477 } 478 479 /** 480 * Write the entire contents of the supplied string to the given writer. This method always flushes and closes the writer when 481 * finished. 482 * 483 * @param content the content to write to the writer; may be null 484 * @param writer the writer to which the content is to be written 485 * @throws IOException 486 * @throws IllegalArgumentException if the writer is null 487 */ 488 public static void write( String content, 489 Writer writer ) throws IOException { 490 IoUtil.write(content, writer); 491 } 492 493 /** 494 * Get the stack trace of the supplied exception. 495 * 496 * @param throwable the exception for which the stack trace is to be returned 497 * @return the stack trace, or null if the supplied exception is null 498 */ 499 public static String getStackTrace( Throwable throwable ) { 500 if (throwable == null) return null; 501 final ByteArrayOutputStream bas = new ByteArrayOutputStream(); 502 final PrintWriter pw = new PrintWriter(bas); 503 throwable.printStackTrace(pw); 504 pw.close(); 505 return bas.toString(); 506 } 507 508 /** 509 * Removes leading and trailing whitespace from the supplied text, and reduces other consecutive whitespace characters to a 510 * single space. Whitespace includes line-feeds. 511 * 512 * @param text the text to be normalized 513 * @return the normalized text 514 */ 515 public static String normalize( String text ) { 516 CheckArg.isNotNull(text, "text"); 517 // This could be much more efficient. 518 return NORMALIZE_PATTERN.matcher(text).replaceAll(" ").trim(); 519 } 520 521 private static final byte[] HEX_CHAR_TABLE = {(byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', 522 (byte)'7', (byte)'8', (byte)'9', (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f'}; 523 524 /** 525 * Get the hexadecimal string representation of the supplied byte array. 526 * 527 * @param bytes the byte array 528 * @return the hex string representation of the byte array; never null 529 */ 530 public static String getHexString( byte[] bytes ) { 531 try { 532 byte[] hex = new byte[2 * bytes.length]; 533 int index = 0; 534 535 for (byte b : bytes) { 536 int v = b & 0xFF; 537 hex[index++] = HEX_CHAR_TABLE[v >>> 4]; 538 hex[index++] = HEX_CHAR_TABLE[v & 0xF]; 539 } 540 return new String(hex, "ASCII"); 541 } catch (UnsupportedEncodingException e) { 542 BigInteger bi = new BigInteger(1, bytes); 543 return String.format("%0" + (bytes.length << 1) + "x", bi); 544 } 545 } 546 547 public static byte[] fromHexString( String hexadecimal ) { 548 int len = hexadecimal.length(); 549 if (len % 2 != 0) { 550 hexadecimal = "0" + hexadecimal; 551 } 552 byte[] data = new byte[len / 2]; 553 for (int i = 0; i < len; i += 2) { 554 data[i / 2] = (byte)((Character.digit(hexadecimal.charAt(i), 16) << 4) + Character.digit(hexadecimal.charAt(i + 1), 555 16)); 556 } 557 return data; 558 } 559 560 public static boolean isHexString( String hexadecimal ) { 561 int len = hexadecimal.length(); 562 for (int i = 0; i < len; ++i) { 563 if (!isHexCharacter(hexadecimal.charAt(i))) return false; 564 } 565 return true; 566 } 567 568 public static boolean isHexCharacter( char c ) { 569 return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); 570 } 571 572 /** 573 * Returns true if the given string is null or represents the empty string 574 * 575 * @param str the string; may be null or empty 576 * @return true if the string is null or contains only whitespace 577 */ 578 public static boolean isBlank( String str ) { 579 return str == null || str.trim().isEmpty(); 580 } 581 582 /** 583 * Returns true if the given string is not null and does not represents the empty string 584 * 585 * @param str the string; may be null or empty 586 * @return true if the string is not null and not empty, false otherwise 587 */ 588 public static boolean notBlank( String str ) { 589 return !isBlank(str); 590 } 591 592 /** 593 * Return whether the supplied string contains any of the supplied characters. 594 * 595 * @param str the string to be examined; may not be null 596 * @param chars the characters to be found within the supplied string; may be zero-length 597 * @return true if the supplied string contains at least one of the supplied characters, or false otherwise 598 */ 599 public static boolean containsAnyOf( String str, 600 char... chars ) { 601 CharacterIterator iter = new StringCharacterIterator(str); 602 for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) { 603 for (char match : chars) { 604 if (c == match) return true; 605 } 606 } 607 return false; 608 } 609 610 private StringUtil() { 611 // Prevent construction 612 } 613}