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