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 }