1 /* 2 * ModeShape (http://www.modeshape.org) 3 * See the COPYRIGHT.txt file distributed with this work for information 4 * regarding copyright ownership. Some portions may be licensed 5 * to Red Hat, Inc. under one or more contributor license agreements. 6 * See the AUTHORS.txt file in the distribution for a full listing of 7 * individual contributors. 8 * 9 * ModeShape is free software. Unless otherwise indicated, all code in ModeShape 10 * is licensed to you under the terms of the GNU Lesser General Public License as 11 * published by the Free Software Foundation; either version 2.1 of 12 * the License, or (at your option) any later version. 13 * 14 * ModeShape is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 * Lesser General Public License for more details. 18 * 19 * You should have received a copy of the GNU Lesser General Public 20 * License along with this software; if not, write to the Free 21 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 22 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 23 */ 24 package org.modeshape.common.util; 25 26 import java.io.ByteArrayOutputStream; 27 import java.io.IOException; 28 import java.io.InputStream; 29 import java.io.OutputStream; 30 import java.io.PrintWriter; 31 import java.io.Reader; 32 import java.io.UnsupportedEncodingException; 33 import java.io.Writer; 34 import java.math.BigInteger; 35 import java.util.Arrays; 36 import java.util.Collections; 37 import java.util.List; 38 import java.util.regex.Matcher; 39 import java.util.regex.Pattern; 40 import net.jcip.annotations.Immutable; 41 import org.modeshape.common.CommonI18n; 42 43 /** 44 * Utilities for string processing and manipulation. 45 */ 46 @Immutable 47 public class StringUtil { 48 49 public static final String[] EMPTY_STRING_ARRAY = new String[0]; 50 private static final Pattern NORMALIZE_PATTERN = Pattern.compile("\\s+"); 51 private static final Pattern PARAMETER_COUNT_PATTERN = Pattern.compile("\\{(\\d+)\\}"); 52 53 /** 54 * Combine the lines into a single string, using the new line character as the delimiter. This is compatible with 55 * {@link #splitLines(String)}. 56 * 57 * @param lines the lines to be combined 58 * @return the combined lines, or an empty string if there are no lines 59 */ 60 public static String combineLines( String[] lines ) { 61 return combineLines(lines, '\n'); 62 } 63 64 /** 65 * Combine the lines into a single string, using the supplied separator as the delimiter. 66 * 67 * @param lines the lines to be combined 68 * @param separator the separator character 69 * @return the combined lines, or an empty string if there are no lines 70 */ 71 public static String combineLines( String[] lines, 72 char separator ) { 73 if (lines == null || lines.length == 0) return ""; 74 StringBuilder sb = new StringBuilder(); 75 for (int i = 0; i != lines.length; ++i) { 76 String line = lines[i]; 77 if (i != 0) sb.append(separator); 78 sb.append(line); 79 } 80 return sb.toString(); 81 } 82 83 /** 84 * Split the supplied content into lines, returning each line as an element in the returned list. 85 * 86 * @param content the string content that is to be split 87 * @return the list of lines; never null but may be an empty (unmodifiable) list if the supplied content is null or empty 88 */ 89 public static List<String> splitLines( final String content ) { 90 if (content == null || content.length() == 0) return Collections.emptyList(); 91 String[] lines = content.split("[\\r]?\\n"); 92 return Arrays.asList(lines); 93 } 94 95 /** 96 * Create a string by substituting the parameters into all key occurrences in the supplied format. The pattern consists of 97 * zero or more keys of the form <code>{n}</code>, where <code>n</code> is an integer starting at 1. Therefore, the first 98 * parameter replaces all occurrences of "{1}", the second parameter replaces all occurrences of "{2}", etc. 99 * <p> 100 * If any parameter is null, the corresponding key is replaced with the string "null". Therefore, consider using an empty 101 * string when keys are to be removed altogether. 102 * </p> 103 * <p> 104 * If there are no parameters, this method does nothing and returns the supplied pattern as is. 105 * </p> 106 * 107 * @param pattern the pattern 108 * @param parameters the parameters used to replace keys 109 * @return the string with all keys replaced (or removed) 110 */ 111 public static String createString( String pattern, 112 Object... parameters ) { 113 CheckArg.isNotNull(pattern, "pattern"); 114 if (parameters == null) parameters = EMPTY_STRING_ARRAY; 115 Matcher matcher = PARAMETER_COUNT_PATTERN.matcher(pattern); 116 StringBuffer text = new StringBuffer(); 117 int requiredParameterCount = 0; 118 boolean err = false; 119 while (matcher.find()) { 120 int ndx = Integer.valueOf(matcher.group(1)); 121 if (requiredParameterCount <= ndx) { 122 requiredParameterCount = ndx + 1; 123 } 124 if (ndx >= parameters.length) { 125 err = true; 126 matcher.appendReplacement(text, matcher.group()); 127 } else { 128 Object parameter = parameters[ndx]; 129 130 // Automatically pretty-print arrays 131 if (parameter != null && parameter.getClass().isArray()) { 132 parameter = Arrays.asList((Object[])parameter); 133 } 134 135 matcher.appendReplacement(text, Matcher.quoteReplacement(parameter == null ? "null" : parameter.toString())); 136 } 137 } 138 if (err || requiredParameterCount < parameters.length) { 139 throw new IllegalArgumentException( 140 CommonI18n.requiredToSuppliedParameterMismatch.text(parameters.length, 141 parameters.length == 1 ? "" : "s", 142 requiredParameterCount, 143 requiredParameterCount == 1 ? "" : "s", 144 pattern, 145 text.toString())); 146 } 147 matcher.appendTail(text); 148 149 return text.toString(); 150 } 151 152 /** 153 * Create a new string containing the specified character repeated a specific number of times. 154 * 155 * @param charToRepeat the character to repeat 156 * @param numberOfRepeats the number of times the character is to repeat in the result; must be greater than 0 157 * @return the resulting string 158 */ 159 public static String createString( final char charToRepeat, 160 int numberOfRepeats ) { 161 assert numberOfRepeats >= 0; 162 StringBuilder sb = new StringBuilder(); 163 for (int i = 0; i < numberOfRepeats; ++i) { 164 sb.append(charToRepeat); 165 } 166 return sb.toString(); 167 } 168 169 /** 170 * Set the length of the string, padding with the supplied character if the supplied string is shorter than desired, or 171 * truncating the string if it is longer than desired. Unlike {@link #justifyLeft(String, int, char)}, this method does not 172 * remove leading and trailing whitespace. 173 * 174 * @param original the string for which the length is to be set; may not be null 175 * @param length the desired length; must be positive 176 * @param padChar the character to use for padding, if the supplied string is not long enough 177 * @return the string of the desired length 178 * @see #justifyLeft(String, int, char) 179 */ 180 public static String setLength( String original, 181 int length, 182 char padChar ) { 183 return justifyLeft(original, length, padChar, false); 184 } 185 186 /** 187 * Right justify the contents of the string, ensuring that the string ends at the last character. If the supplied string is 188 * longer than the desired width, the leading characters are removed so that the last character in the supplied string at the 189 * last position. If the supplied string is shorter than the desired width, the padding character is inserted one or more 190 * times such that the last character in the supplied string appears as the last character in the resulting string and that 191 * the length matches that specified. 192 * 193 * @param str the string to be right justified; if null, an empty string is used 194 * @param width the desired width of the string; must be positive 195 * @param padWithChar the character to use for padding, if needed 196 * @return the right justified string 197 */ 198 public static String justifyRight( String str, 199 final int width, 200 char padWithChar ) { 201 assert width > 0; 202 // Trim the leading and trailing whitespace ... 203 str = str != null ? str.trim() : ""; 204 205 final int length = str.length(); 206 int addChars = width - length; 207 if (addChars < 0) { 208 // truncate the first characters, keep the last 209 return str.subSequence(length - width, length).toString(); 210 } 211 // Prepend the whitespace ... 212 final StringBuilder sb = new StringBuilder(); 213 while (addChars > 0) { 214 sb.append(padWithChar); 215 --addChars; 216 } 217 218 // Write the content ... 219 sb.append(str); 220 return sb.toString(); 221 } 222 223 /** 224 * Left justify the contents of the string, ensuring that the supplied string begins at the first character and that the 225 * resulting string is of the desired length. If the supplied string is longer than the desired width, it is truncated to the 226 * specified length. If the supplied string is shorter than the desired width, the padding character is added to the end of 227 * the string one or more times such that the length is that specified. All leading and trailing whitespace is removed. 228 * 229 * @param str the string to be left justified; if null, an empty string is used 230 * @param width the desired width of the string; must be positive 231 * @param padWithChar the character to use for padding, if needed 232 * @return the left justified string 233 * @see #setLength(String, int, char) 234 */ 235 public static String justifyLeft( String str, 236 final int width, 237 char padWithChar ) { 238 return justifyLeft(str, width, padWithChar, true); 239 } 240 241 protected static String justifyLeft( String str, 242 final int width, 243 char padWithChar, 244 boolean trimWhitespace ) { 245 // Trim the leading and trailing whitespace ... 246 str = str != null ? (trimWhitespace ? str.trim() : str) : ""; 247 248 int addChars = width - str.length(); 249 if (addChars < 0) { 250 // truncate 251 return str.subSequence(0, width).toString(); 252 } 253 // Write the content ... 254 final StringBuilder sb = new StringBuilder(); 255 sb.append(str); 256 257 // Append the whitespace ... 258 while (addChars > 0) { 259 sb.append(padWithChar); 260 --addChars; 261 } 262 263 return sb.toString(); 264 } 265 266 /** 267 * Center the contents of the string. If the supplied string is longer than the desired width, it is truncated to the 268 * specified length. If the supplied string is shorter than the desired width, padding characters are added to the beginning 269 * and end of the string such that the length is that specified; one additional padding character is prepended if required. 270 * All leading and trailing whitespace is removed before centering. 271 * 272 * @param str the string to be left justified; if null, an empty string is used 273 * @param width the desired width of the string; must be positive 274 * @param padWithChar the character to use for padding, if needed 275 * @return the left justified string 276 * @see #setLength(String, int, char) 277 */ 278 public static String justifyCenter( String str, 279 final int width, 280 char padWithChar ) { 281 // Trim the leading and trailing whitespace ... 282 str = str != null ? str.trim() : ""; 283 284 int addChars = width - str.length(); 285 if (addChars < 0) { 286 // truncate 287 return str.subSequence(0, width).toString(); 288 } 289 // Write the content ... 290 int prependNumber = addChars / 2; 291 int appendNumber = prependNumber; 292 if ((prependNumber + appendNumber) != addChars) { 293 ++prependNumber; 294 } 295 296 final StringBuilder sb = new StringBuilder(); 297 298 // Prepend the pad character(s) ... 299 while (prependNumber > 0) { 300 sb.append(padWithChar); 301 --prependNumber; 302 } 303 304 // Add the actual content 305 sb.append(str); 306 307 // Append the pad character(s) ... 308 while (appendNumber > 0) { 309 sb.append(padWithChar); 310 --appendNumber; 311 } 312 313 return sb.toString(); 314 } 315 316 /** 317 * Truncate the supplied string to be no more than the specified length. This method returns an empty string if the supplied 318 * object is null. 319 * 320 * @param obj the object from which the string is to be obtained using {@link Object#toString()}. 321 * @param maxLength the maximum length of the string being returned 322 * @return the supplied string if no longer than the maximum length, or the supplied string truncated to be no longer than the 323 * maximum length (including the suffix) 324 * @throws IllegalArgumentException if the maximum length is negative 325 */ 326 public static String truncate( Object obj, 327 int maxLength ) { 328 return truncate(obj, maxLength, null); 329 } 330 331 /** 332 * Truncate the supplied string to be no more than the specified length. This method returns an empty string if the supplied 333 * object is null. 334 * 335 * @param obj the object from which the string is to be obtained using {@link Object#toString()}. 336 * @param maxLength the maximum length of the string being returned 337 * @param suffix the suffix that should be added to the content if the string must be truncated, or null if the default suffix 338 * of "..." should be used 339 * @return the supplied string if no longer than the maximum length, or the supplied string truncated to be no longer than the 340 * maximum length (including the suffix) 341 * @throws IllegalArgumentException if the maximum length is negative 342 */ 343 public static String truncate( Object obj, 344 int maxLength, 345 String suffix ) { 346 CheckArg.isNonNegative(maxLength, "maxLength"); 347 if (obj == null || maxLength == 0) { 348 return ""; 349 } 350 String str = obj.toString(); 351 if (str.length() <= maxLength) return str; 352 if (suffix == null) suffix = "..."; 353 int maxNumChars = maxLength - suffix.length(); 354 if (maxNumChars < 0) { 355 // Then the max length is actually shorter than the suffix ... 356 str = suffix.substring(0, maxLength); 357 } else if (str.length() > maxNumChars) { 358 str = str.substring(0, maxNumChars) + suffix; 359 } 360 return str; 361 } 362 363 /** 364 * Read and return the entire contents of the supplied {@link Reader}. This method always closes the reader when finished 365 * reading. 366 * 367 * @param reader the reader of the contents; may be null 368 * @return the contents, or an empty string if the supplied reader is null 369 * @throws IOException if there is an error reading the content 370 */ 371 public static String read( Reader reader ) throws IOException { 372 return IoUtil.read(reader); 373 } 374 375 /** 376 * Read and return the entire contents of the supplied {@link InputStream}. This method always closes the stream when finished 377 * reading. 378 * 379 * @param stream the streamed contents; may be null 380 * @return the contents, or an empty string if the supplied stream is null 381 * @throws IOException if there is an error reading the content 382 */ 383 public static String read( InputStream stream ) throws IOException { 384 return IoUtil.read(stream); 385 } 386 387 /** 388 * Write the entire contents of the supplied string to the given stream. This method always flushes and closes the stream when 389 * finished. 390 * 391 * @param content the content to write to the stream; may be null 392 * @param stream the stream to which the content is to be written 393 * @throws IOException 394 * @throws IllegalArgumentException if the stream is null 395 */ 396 public static void write( String content, 397 OutputStream stream ) throws IOException { 398 IoUtil.write(content, stream); 399 } 400 401 /** 402 * Write the entire contents of the supplied string to the given writer. This method always flushes and closes the writer when 403 * finished. 404 * 405 * @param content the content to write to the writer; may be null 406 * @param writer the writer to which the content is to be written 407 * @throws IOException 408 * @throws IllegalArgumentException if the writer is null 409 */ 410 public static void write( String content, 411 Writer writer ) throws IOException { 412 IoUtil.write(content, writer); 413 } 414 415 /** 416 * Get the stack trace of the supplied exception. 417 * 418 * @param throwable the exception for which the stack trace is to be returned 419 * @return the stack trace, or null if the supplied exception is null 420 */ 421 public static String getStackTrace( Throwable throwable ) { 422 if (throwable == null) return null; 423 final ByteArrayOutputStream bas = new ByteArrayOutputStream(); 424 final PrintWriter pw = new PrintWriter(bas); 425 throwable.printStackTrace(pw); 426 pw.close(); 427 return bas.toString(); 428 } 429 430 /** 431 * Removes leading and trailing whitespace from the supplied text, and reduces other consecutive whitespace characters to a 432 * single space. Whitespace includes line-feeds. 433 * 434 * @param text the text to be normalized 435 * @return the normalized text 436 */ 437 public static String normalize( String text ) { 438 CheckArg.isNotNull(text, "text"); 439 // This could be much more efficient. 440 return NORMALIZE_PATTERN.matcher(text).replaceAll(" ").trim(); 441 } 442 443 private static final byte[] HEX_CHAR_TABLE = {(byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', 444 (byte)'7', (byte)'8', (byte)'9', (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f'}; 445 446 /** 447 * Get the hexadecimal string representation of the supplied byte array. 448 * 449 * @param bytes the byte array 450 * @return the hex string representation of the byte array; never null 451 */ 452 public static String getHexString( byte[] bytes ) { 453 try { 454 byte[] hex = new byte[2 * bytes.length]; 455 int index = 0; 456 457 for (byte b : bytes) { 458 int v = b & 0xFF; 459 hex[index++] = HEX_CHAR_TABLE[v >>> 4]; 460 hex[index++] = HEX_CHAR_TABLE[v & 0xF]; 461 } 462 return new String(hex, "ASCII"); 463 } catch (UnsupportedEncodingException e) { 464 BigInteger bi = new BigInteger(1, bytes); 465 return String.format("%0" + (bytes.length << 1) + "x", bi); 466 } 467 } 468 469 public static byte[] fromHexString( String hexadecimal ) { 470 int len = hexadecimal.length(); 471 if (len % 2 != 0) { 472 hexadecimal = "0" + hexadecimal; 473 } 474 byte[] data = new byte[len / 2]; 475 for (int i = 0; i < len; i += 2) { 476 data[i / 2] = (byte)((Character.digit(hexadecimal.charAt(i), 16) << 4) + Character.digit(hexadecimal.charAt(i + 1), 477 16)); 478 } 479 return data; 480 } 481 482 private StringUtil() { 483 // Prevent construction 484 } 485 }