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