001 /* 002 * JBoss DNA (http://www.jboss.org/dna) 003 * See the COPYRIGHT.txt file distributed with this work for information 004 * regarding copyright ownership. Some portions may be licensed 005 * to Red Hat, Inc. under one or more contributor license agreements. 006 * See the AUTHORS.txt file in the distribution for a full listing of 007 * individual contributors. 008 * 009 * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA 010 * is licensed to you under the terms of the GNU Lesser General Public License as 011 * published by the Free Software Foundation; either version 2.1 of 012 * the License, or (at your option) any later version. 013 * 014 * JBoss DNA is distributed in the hope that it will be useful, 015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 017 * Lesser General Public License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this software; if not, write to the Free 021 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 022 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 023 */ 024 package org.jboss.dna.common.util; 025 026 import java.io.ByteArrayOutputStream; 027 import java.io.IOException; 028 import java.io.InputStream; 029 import java.io.OutputStream; 030 import java.io.PrintWriter; 031 import java.io.Reader; 032 import java.io.UnsupportedEncodingException; 033 import java.io.Writer; 034 import java.util.Arrays; 035 import java.util.Collections; 036 import java.util.List; 037 import java.util.regex.Matcher; 038 import java.util.regex.Pattern; 039 import org.jboss.dna.common.CommonI18n; 040 041 /** 042 * Utilities for string processing and manipulation. 043 */ 044 public class StringUtil { 045 046 public static final String[] EMPTY_STRING_ARRAY = new String[0]; 047 private static final Pattern NORMALIZE_PATTERN = Pattern.compile("\\s+"); 048 private static final Pattern PARAMETER_COUNT_PATTERN = Pattern.compile("\\{(\\d+)\\}"); 049 050 /** 051 * Combine the lines into a single string, using the new line character as the delimiter. This is compatible with 052 * {@link #splitLines(String)}. 053 * 054 * @param lines the lines to be combined 055 * @return the combined lines, or an empty string if there are no lines 056 */ 057 public static String combineLines( String[] lines ) { 058 return combineLines(lines, '\n'); 059 } 060 061 /** 062 * Combine the lines into a single string, using the supplied separator as the delimiter. 063 * 064 * @param lines the lines to be combined 065 * @param separator the separator character 066 * @return the combined lines, or an empty string if there are no lines 067 */ 068 public static String combineLines( String[] lines, 069 char separator ) { 070 if (lines == null || lines.length == 0) return ""; 071 StringBuilder sb = new StringBuilder(); 072 for (int i = 0; i != lines.length; ++i) { 073 String line = lines[i]; 074 if (i != 0) sb.append(separator); 075 sb.append(line); 076 } 077 return sb.toString(); 078 } 079 080 /** 081 * Split the supplied content into lines, returning each line as an element in the returned list. 082 * 083 * @param content the string content that is to be split 084 * @return the list of lines; never null but may be an empty (unmodifiable) list if the supplied content is null or empty 085 */ 086 public static List<String> splitLines( final String content ) { 087 if (content == null || content.length() == 0) return Collections.emptyList(); 088 String[] lines = content.split("[\\r]?\\n"); 089 return Arrays.asList(lines); 090 } 091 092 /** 093 * Create a string by substituting the parameters into all key occurrences in the supplied format. The pattern consists of 094 * zero or more keys of the form <code>{n}</code>, where <code>n</code> is an integer starting at 1. Therefore, the first 095 * parameter replaces all occurrences of "{1}", the second parameter replaces all occurrences of "{2}", etc. 096 * <p> 097 * If any parameter is null, the corresponding key is replaced with the string "null". Therefore, consider using an empty 098 * string when keys are to be removed altogether. 099 * </p> 100 * <p> 101 * If there are no parameters, this method does nothing and returns the supplied pattern as is. 102 * </p> 103 * 104 * @param pattern the pattern 105 * @param parameters the parameters used to replace keys 106 * @return the string with all keys replaced (or removed) 107 */ 108 public static String createString( String pattern, 109 Object... parameters ) { 110 CheckArg.isNotNull(pattern, "pattern"); 111 if (parameters == null) parameters = EMPTY_STRING_ARRAY; 112 Matcher matcher = PARAMETER_COUNT_PATTERN.matcher(pattern); 113 StringBuffer text = new StringBuffer(); 114 int requiredParameterCount = 0; 115 boolean err = false; 116 while (matcher.find()) { 117 int ndx = Integer.valueOf(matcher.group(1)); 118 if (requiredParameterCount <= ndx) { 119 requiredParameterCount = ndx + 1; 120 } 121 if (ndx >= parameters.length) { 122 err = true; 123 matcher.appendReplacement(text, matcher.group()); 124 } else { 125 Object parameter = parameters[ndx]; 126 matcher.appendReplacement(text, Matcher.quoteReplacement(parameter == null ? "null" : parameter.toString())); 127 } 128 } 129 if (err || requiredParameterCount < parameters.length) { 130 throw new IllegalArgumentException( 131 CommonI18n.requiredToSuppliedParameterMismatch.text(parameters.length, 132 parameters.length == 1 ? "" : "s", 133 requiredParameterCount, 134 requiredParameterCount == 1 ? "" : "s", 135 pattern, 136 text.toString())); 137 } 138 matcher.appendTail(text); 139 140 return text.toString(); 141 } 142 143 /** 144 * Create a new string containing the specified character repeated a specific number of times. 145 * 146 * @param charToRepeat the character to repeat 147 * @param numberOfRepeats the number of times the character is to repeat in the result; must be greater than 0 148 * @return the resulting string 149 */ 150 public static String createString( final char charToRepeat, 151 int numberOfRepeats ) { 152 assert numberOfRepeats >= 0; 153 StringBuilder sb = new StringBuilder(); 154 for (int i = 0; i < numberOfRepeats; ++i) { 155 sb.append(charToRepeat); 156 } 157 return sb.toString(); 158 } 159 160 /** 161 * Set the length of the string, padding with the supplied character if the supplied string is shorter than desired, or 162 * truncating the string if it is longer than desired. Unlike {@link #justifyLeft(String, int, char)}, this method does not 163 * remove leading and trailing whitespace. 164 * 165 * @param original the string for which the length is to be set; may not be null 166 * @param length the desired length; must be positive 167 * @param padChar the character to use for padding, if the supplied string is not long enough 168 * @return the string of the desired length 169 * @see #justifyLeft(String, int, char) 170 */ 171 public static String setLength( String original, 172 int length, 173 char padChar ) { 174 return justifyLeft(original, length, padChar, false); 175 } 176 177 /** 178 * Right justify the contents of the string, ensuring that the string ends at the last character. If the supplied string is 179 * longer than the desired width, the leading characters are removed so that the last character in the supplied string at the 180 * last position. If the supplied string is shorter than the desired width, the padding character is inserted one or more 181 * times such that the last character in the supplied string appears as the last character in the resulting string and that 182 * the length matches that specified. 183 * 184 * @param str the string to be right justified; if null, an empty string is used 185 * @param width the desired width of the string; must be positive 186 * @param padWithChar the character to use for padding, if needed 187 * @return the right justified string 188 */ 189 public static String justifyRight( String str, 190 final int width, 191 char padWithChar ) { 192 assert width > 0; 193 // Trim the leading and trailing whitespace ... 194 str = str != null ? str.trim() : ""; 195 196 final int length = str.length(); 197 int addChars = width - length; 198 if (addChars < 0) { 199 // truncate the first characters, keep the last 200 return str.subSequence(length - width, length).toString(); 201 } 202 // Prepend the whitespace ... 203 final StringBuilder sb = new StringBuilder(); 204 while (addChars > 0) { 205 sb.append(padWithChar); 206 --addChars; 207 } 208 209 // Write the content ... 210 sb.append(str); 211 return sb.toString(); 212 } 213 214 /** 215 * Left justify the contents of the string, ensuring that the supplied string begins at the first character and that the 216 * resulting string is of the desired length. If the supplied string is longer than the desired width, it is truncated to the 217 * specified length. If the supplied string is shorter than the desired width, the padding character is added to the end of 218 * the string one or more times such that the length is that specified. All leading and trailing whitespace is removed. 219 * 220 * @param str the string to be left justified; if null, an empty string is used 221 * @param width the desired width of the string; must be positive 222 * @param padWithChar the character to use for padding, if needed 223 * @return the left justified string 224 * @see #setLength(String, int, char) 225 */ 226 public static String justifyLeft( String str, 227 final int width, 228 char padWithChar ) { 229 return justifyLeft(str, width, padWithChar, true); 230 } 231 232 protected static String justifyLeft( String str, 233 final int width, 234 char padWithChar, 235 boolean trimWhitespace ) { 236 // Trim the leading and trailing whitespace ... 237 str = str != null ? (trimWhitespace ? str.trim() : str) : ""; 238 239 int addChars = width - str.length(); 240 if (addChars < 0) { 241 // truncate 242 return str.subSequence(0, width).toString(); 243 } 244 // Write the content ... 245 final StringBuilder sb = new StringBuilder(); 246 sb.append(str); 247 248 // Append the whitespace ... 249 while (addChars > 0) { 250 sb.append(padWithChar); 251 --addChars; 252 } 253 254 return sb.toString(); 255 } 256 257 /** 258 * Center the contents of the string. If the supplied string is longer than the desired width, it is truncated to the 259 * specified length. If the supplied string is shorter than the desired width, padding characters are added to the beginning 260 * and end of the string such that the length is that specified; one additional padding character is prepended if required. 261 * All leading and trailing whitespace is removed before centering. 262 * 263 * @param str the string to be left justified; if null, an empty string is used 264 * @param width the desired width of the string; must be positive 265 * @param padWithChar the character to use for padding, if needed 266 * @return the left justified string 267 * @see #setLength(String, int, char) 268 */ 269 public static String justifyCenter( String str, 270 final int width, 271 char padWithChar ) { 272 // Trim the leading and trailing whitespace ... 273 str = str != null ? str.trim() : ""; 274 275 int addChars = width - str.length(); 276 if (addChars < 0) { 277 // truncate 278 return str.subSequence(0, width).toString(); 279 } 280 // Write the content ... 281 int prependNumber = addChars / 2; 282 int appendNumber = prependNumber; 283 if ((prependNumber + appendNumber) != addChars) { 284 ++prependNumber; 285 } 286 287 final StringBuilder sb = new StringBuilder(); 288 289 // Prepend the pad character(s) ... 290 while (prependNumber > 0) { 291 sb.append(padWithChar); 292 --prependNumber; 293 } 294 295 // Add the actual content 296 sb.append(str); 297 298 // Append the pad character(s) ... 299 while (appendNumber > 0) { 300 sb.append(padWithChar); 301 --appendNumber; 302 } 303 304 return sb.toString(); 305 } 306 307 /** 308 * Truncate the supplied string to be no more than the specified length. This method returns an empty string if the supplied 309 * object is null. 310 * 311 * @param obj the object from which the string is to be obtained using {@link Object#toString()}. 312 * @param maxLength the maximum length of the string being returned 313 * @return the supplied string if no longer than the maximum length, or the supplied string truncated to be no longer than the 314 * maximum length (including the suffix) 315 * @throws IllegalArgumentException if the maximum length is negative 316 */ 317 public static String truncate( Object obj, 318 int maxLength ) { 319 return truncate(obj, maxLength, null); 320 } 321 322 /** 323 * Truncate the supplied string to be no more than the specified length. This method returns an empty string if the supplied 324 * object is null. 325 * 326 * @param obj the object from which the string is to be obtained using {@link Object#toString()}. 327 * @param maxLength the maximum length of the string being returned 328 * @param suffix the suffix that should be added to the content if the string must be truncated, or null if the default suffix 329 * of "..." should be used 330 * @return the supplied string if no longer than the maximum length, or the supplied string truncated to be no longer than the 331 * maximum length (including the suffix) 332 * @throws IllegalArgumentException if the maximum length is negative 333 */ 334 public static String truncate( Object obj, 335 int maxLength, 336 String suffix ) { 337 CheckArg.isNonNegative(maxLength, "maxLength"); 338 if (obj == null || maxLength == 0) { 339 return ""; 340 } 341 String str = obj.toString(); 342 if (str.length() <= maxLength) return str; 343 if (suffix == null) suffix = "..."; 344 int maxNumChars = maxLength - suffix.length(); 345 if (maxNumChars < 0) { 346 // Then the max length is actually shorter than the suffix ... 347 str = suffix.substring(0, maxLength); 348 } else if (str.length() > maxNumChars) { 349 str = str.substring(0, maxNumChars) + suffix; 350 } 351 return str; 352 } 353 354 /** 355 * Read and return the entire contents of the supplied {@link Reader}. This method always closes the reader when finished 356 * reading. 357 * 358 * @param reader the reader of the contents; may be null 359 * @return the contents, or an empty string if the supplied reader is null 360 * @throws IOException if there is an error reading the content 361 */ 362 public static String read( Reader reader ) throws IOException { 363 return IoUtil.read(reader); 364 } 365 366 /** 367 * Read and return the entire contents of the supplied {@link InputStream}. This method always closes the stream when finished 368 * reading. 369 * 370 * @param stream the streamed contents; may be null 371 * @return the contents, or an empty string if the supplied stream is null 372 * @throws IOException if there is an error reading the content 373 */ 374 public static String read( InputStream stream ) throws IOException { 375 return IoUtil.read(stream); 376 } 377 378 /** 379 * Write the entire contents of the supplied string to the given stream. This method always flushes and closes the stream when 380 * finished. 381 * 382 * @param content the content to write to the stream; may be null 383 * @param stream the stream to which the content is to be written 384 * @throws IOException 385 * @throws IllegalArgumentException if the stream is null 386 */ 387 public static void write( String content, 388 OutputStream stream ) throws IOException { 389 IoUtil.write(content, stream); 390 } 391 392 /** 393 * Write the entire contents of the supplied string to the given writer. This method always flushes and closes the writer when 394 * finished. 395 * 396 * @param content the content to write to the writer; may be null 397 * @param writer the writer to which the content is to be written 398 * @throws IOException 399 * @throws IllegalArgumentException if the writer is null 400 */ 401 public static void write( String content, 402 Writer writer ) throws IOException { 403 IoUtil.write(content, writer); 404 } 405 406 /** 407 * Get the stack trace of the supplied exception. 408 * 409 * @param throwable the exception for which the stack trace is to be returned 410 * @return the stack trace, or null if the supplied exception is null 411 */ 412 public static String getStackTrace( Throwable throwable ) { 413 if (throwable == null) return null; 414 final ByteArrayOutputStream bas = new ByteArrayOutputStream(); 415 final PrintWriter pw = new PrintWriter(bas); 416 throwable.printStackTrace(pw); 417 pw.close(); 418 return bas.toString(); 419 } 420 421 /** 422 * Removes leading and trailing whitespace from the supplied text, and reduces other consecutive whitespace characters to a 423 * single space. Whitespace includes line-feeds. 424 * 425 * @param text the text to be normalized 426 * @return the normalized text 427 */ 428 public static String normalize( String text ) { 429 CheckArg.isNotNull(text, "text"); 430 // This could be much more efficient. 431 return NORMALIZE_PATTERN.matcher(text).replaceAll(" ").trim(); 432 } 433 434 private static final byte[] HEX_CHAR_TABLE = {(byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', 435 (byte)'7', (byte)'8', (byte)'9', (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f'}; 436 437 /** 438 * Get the hexadecimal string representation of the supplied byte array. 439 * 440 * @param bytes the byte array 441 * @return the hex string representation of the byte array; never null 442 * @throws UnsupportedEncodingException 443 */ 444 public static String getHexString( byte[] bytes ) throws UnsupportedEncodingException { 445 byte[] hex = new byte[2 * bytes.length]; 446 int index = 0; 447 448 for (byte b : bytes) { 449 int v = b & 0xFF; 450 hex[index++] = HEX_CHAR_TABLE[v >>> 4]; 451 hex[index++] = HEX_CHAR_TABLE[v & 0xF]; 452 } 453 return new String(hex, "ASCII"); 454 } 455 456 private StringUtil() { 457 // Prevent construction 458 } 459 }