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 }