StringUtil xref
1 /**
2 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3 */
4 package net.sourceforge.pmd.util;
5
6 import java.util.ArrayList;
7 import java.util.Iterator;
8 import java.util.List;
9
10 /**
11 * A number of String-specific utility methods for use by PMD or its IDE plugins.
12 *
13 * @author BrianRemedios
14 */
15 public final class StringUtil {
16
17 public static final String[] EMPTY_STRINGS = new String[0];
18 private static final boolean SUPPORTS_UTF8 = System.getProperty("net.sourceforge.pmd.supportUTF8", "no").equals("yes");
19
20 private StringUtil() {}
21
22 /**
23 * Return whether the non-null text arg starts with any of the prefix
24 * values.
25 *
26 * @param text
27 * @param prefixes
28 * @return boolean
29 */
30 public static boolean startsWithAny(String text, String... prefixes) {
31
32 for (String prefix : prefixes) {
33 if (text.startsWith(prefix)) return true;
34 }
35
36 return false;
37 }
38
39 /**
40 * Returns whether the non-null text arg matches any of the test values.
41 *
42 * @param text
43 * @param tests
44 * @return boolean
45 */
46 public static boolean isAnyOf(String text, String... tests) {
47
48 for (String test : tests) {
49 if (text.equals(test)) return true;
50 }
51
52 return false;
53 }
54
55 /**
56 * Checks for the existence of any of the listed prefixes on the
57 * non-null text and removes them.
58 *
59 * @param text
60 * @param prefixes
61 * @return String
62 */
63 public static String withoutPrefixes(String text, String... prefixes) {
64
65 for (String prefix : prefixes) {
66 if (text.startsWith(prefix)) {
67 return text.substring(prefix.length());
68 }
69 }
70
71 return text;
72 }
73
74 /**
75 * Returns true if the value arg is either null, empty, or full of whitespace characters.
76 * More efficient that calling (string).trim().length() == 0
77 *
78 * @param value
79 * @return <code>true</code> if the value is empty, <code>false</code> otherwise.
80 */
81 public static boolean isEmpty(String value) {
82
83 if (value == null || "".equals(value)) {
84 return true;
85 }
86
87 for (int i=0; i<value.length(); i++) {
88 if (!Character.isWhitespace(value.charAt(i))) {
89 return false;
90 }
91 }
92
93 return true;
94 }
95
96 /**
97 *
98 * @param value String
99 * @return boolean
100 */
101 public static boolean isNotEmpty(String value) {
102 return !isEmpty(value);
103 }
104
105 /**
106 * Returns true if both strings are effectively null or whitespace,
107 * returns false otherwise if they have actual text that differs.
108 *
109 * @param a
110 * @param b
111 * @return boolean
112 */
113 public static boolean areSemanticEquals(String a, String b) {
114
115 if (a==null) { return isEmpty(b); }
116 if (b==null) { return isEmpty(a); }
117
118 return a.equals(b);
119 }
120
121 /**
122 *
123 * @param original String
124 * @param oldChar char
125 * @param newString String
126 * @return String
127 */
128 public static String replaceString(final String original, char oldChar, final String newString) {
129 int index = original.indexOf(oldChar);
130 if (index < 0) {
131 return original;
132 } else {
133 final String replace = newString == null ? "" : newString;
134 final StringBuilder buf = new StringBuilder(Math.max(16, original.length() + replace.length()));
135 int last = 0;
136 while (index != -1) {
137 buf.append(original.substring(last, index));
138 buf.append(replace);
139 last = index + 1;
140 index = original.indexOf(oldChar, last);
141 }
142 buf.append(original.substring(last));
143 return buf.toString();
144 }
145 }
146
147 /**
148 *
149 * @param original String
150 * @param oldString String
151 * @param newString String
152 * @return String
153 */
154 public static String replaceString(final String original, final String oldString, final String newString) {
155 int index = original.indexOf(oldString);
156 if (index < 0) {
157 return original;
158 } else {
159 final String replace = newString == null ? "" : newString;
160 final StringBuilder buf = new StringBuilder(Math.max(16, original.length() + replace.length()));
161 int last = 0;
162 while (index != -1) {
163 buf.append(original.substring(last, index));
164 buf.append(replace);
165 last = index + oldString.length();
166 index = original.indexOf(oldString, last);
167 }
168 buf.append(original.substring(last));
169 return buf.toString();
170 }
171 }
172
173 /**
174 * Appends to a StringBuilder the String src where non-ASCII and
175 * XML special chars are escaped.
176 *
177 * @param buf The destination XML stream
178 * @param src The String to append to the stream
179 */
180 public static void appendXmlEscaped(StringBuilder buf, String src) {
181 appendXmlEscaped(buf, src, SUPPORTS_UTF8);
182 }
183
184 /**
185 * Replace some whitespace characters so they are visually apparent.
186 *
187 * @param o
188 * @return String
189 */
190 public static String escapeWhitespace(Object o) {
191
192 if (o == null) {
193 return null;
194 }
195 String s = String.valueOf(o);
196 s = s.replace("\n", "\\n");
197 s = s.replace("\r", "\\r");
198 s = s.replace("\t", "\\t");
199 return s;
200 }
201
202 /**
203 *
204 * @param string String
205 * @return String
206 */
207 public static String htmlEncode(String string) {
208 String encoded = replaceString(string, '&', "&");
209 encoded = replaceString(encoded, '<', "<");
210 return replaceString(encoded, '>', ">");
211 }
212
213 /**
214 *
215 * @param buf
216 * @param src
217 * @param supportUTF8 override the default setting, whether special characters should be replaced
218 * with entities (<code>false</code>) or should be included as is (<code>true</code>).
219 * @see #appendXmlEscaped(StringBuilder, String)
220 *
221 * TODO - unify the method above with the one below
222 *
223 * public to support unit testing - make this package private, once the unit test classes are in the same package.
224 */
225 public static void appendXmlEscaped(StringBuilder buf, String src, boolean supportUTF8) {
226 char c;
227 for (int i = 0; i < src.length(); i++) {
228 c = src.charAt(i);
229 if (c > '~') {// 126
230 if (!supportUTF8) {
231 buf.append("&#x").append(Integer.toHexString(c)).append(';');
232 } else {
233 buf.append(c);
234 }
235 } else if (c == '&') {
236 buf.append("&");
237 } else if (c == '"') {
238 buf.append(""");
239 } else if (c == '<') {
240 buf.append("<");
241 } else if (c == '>') {
242 buf.append(">");
243 } else {
244 buf.append(c);
245 }
246 }
247 }
248
249 /**
250 * Parses the input source using the delimiter specified. This method is much
251 * faster than using the StringTokenizer or String.split(char) approach and
252 * serves as a replacement for String.split() for JDK1.3 that doesn't have it.
253 *
254 * FIXME - we're on JDK 1.4 now, can we replace this with String.split?
255 *
256 * @param source String
257 * @param delimiter char
258 * @return String[]
259 */
260 public static String[] substringsOf(String source, char delimiter) {
261
262 if (source == null || source.length() == 0) {
263 return EMPTY_STRINGS;
264 }
265
266 int delimiterCount = 0;
267 int length = source.length();
268 char[] chars = source.toCharArray();
269
270 for (int i=0; i<length; i++) {
271 if (chars[i] == delimiter) {
272 delimiterCount++;
273 }
274 }
275
276 if (delimiterCount == 0) {
277 return new String[] { source };
278 }
279
280 String[] results = new String[delimiterCount+1];
281
282 int i = 0;
283 int offset = 0;
284
285 while (offset <= length) {
286 int pos = source.indexOf(delimiter, offset);
287 if (pos < 0) {
288 pos = length;
289 }
290 results[i++] = pos == offset ? "" : source.substring(offset, pos);
291 offset = pos + 1;
292 }
293
294 return results;
295 }
296
297 /**
298 * Much more efficient than StringTokenizer.
299 *
300 * @param str String
301 * @param separator char
302 * @return String[]
303 */
304 public static String[] substringsOf(String str, String separator) {
305
306 if (str == null || str.length() == 0) {
307 return EMPTY_STRINGS;
308 }
309
310 int index = str.indexOf(separator);
311 if (index == -1) {
312 return new String[]{str};
313 }
314
315 List<String> list = new ArrayList<String>();
316 int currPos = 0;
317 int len = separator.length();
318 while (index != -1) {
319 list.add(str.substring(currPos, index));
320 currPos = index + len;
321 index = str.indexOf(separator, currPos);
322 }
323 list.add(str.substring(currPos));
324 return list.toArray(new String[list.size()]);
325 }
326
327
328 /**
329 * Copies the elements returned by the iterator onto the string buffer
330 * each delimited by the separator.
331 *
332 * @param sb StringBuffer
333 * @param iter Iterator
334 * @param separator String
335 */
336 public static void asStringOn(StringBuffer sb, Iterator<?> iter, String separator) {
337
338 if (!iter.hasNext()) { return; }
339
340 sb.append(iter.next());
341
342 while (iter.hasNext()) {
343 sb.append(separator);
344 sb.append(iter.next());
345 }
346 }
347
348 /**
349 * Copies the array items onto the string builder each delimited by the separator.
350 * Does nothing if the array is null or empty.
351 *
352 * @param sb StringBuilder
353 * @param items Object[]
354 * @param separator String
355 */
356 public static void asStringOn(StringBuilder sb, Object[] items, String separator) {
357
358 if (items == null || items.length == 0) { return; }
359
360 sb.append(items[0]);
361
362 for (int i=1; i<items.length; i++) {
363 sb.append(separator);
364 sb.append(items[i]);
365 }
366 }
367
368 /**
369 * Return the length of the shortest string in the array.
370 * If the collection is empty or any one of them is
371 * null then it returns 0.
372 *
373 * @param strings String[]
374 * @return int
375 */
376 public static int lengthOfShortestIn(String[] strings) {
377
378 if (CollectionUtil.isEmpty(strings)) { return 0; }
379
380 int minLength = Integer.MAX_VALUE;
381
382 for (int i=0; i<strings.length; i++) {
383 if (strings[i] == null) {
384 return 0;
385 }
386 minLength = Math.min(minLength, strings[i].length());
387 }
388
389 return minLength;
390 }
391
392 /**
393 * Determine the maximum number of common leading whitespace characters
394 * the strings share in the same sequence. Useful for determining how
395 * many leading characters can be removed to shift all the text in the
396 * strings to the left without misaligning them.
397 *
398 * @param strings String[]
399 * @return int
400 */
401 public static int maxCommonLeadingWhitespaceForAll(String[] strings) {
402
403 int shortest = lengthOfShortestIn(strings);
404 if (shortest == 0) {
405 return 0;
406 }
407
408 char[] matches = new char[shortest];
409
410 String str;
411 for (int m=0; m<matches.length; m++) {
412 matches[m] = strings[0].charAt(m);
413 if (!Character.isWhitespace(matches[m])) {
414 return m;
415 }
416 for (int i=0; i<strings.length; i++) {
417 str = strings[i];
418 if (str.charAt(m) != matches[m]) {
419 return m;
420 }
421 }
422 }
423
424 return shortest;
425 }
426
427 /**
428 * Trims off the leading characters off the strings up to the trimDepth
429 * specified. Returns the same strings if trimDepth = 0
430 *
431 * @param strings
432 * @param trimDepth
433 * @return String[]
434 */
435 public static String[] trimStartOn(String[] strings, int trimDepth) {
436
437 if (trimDepth == 0) {
438 return strings;
439 }
440
441 String[] results = new String[strings.length];
442 for (int i=0; i<strings.length; i++) {
443 results[i] = strings[i].substring(trimDepth);
444 }
445 return results;
446 }
447
448 /**
449 * Left pads a string.
450 * @param s The String to pad
451 * @param length The desired minimum length of the resulting padded String
452 * @return The resulting left padded String
453 */
454 public static String lpad(String s, int length) {
455 String res = s;
456 if (length - s.length() > 0) {
457 char [] arr = new char[length - s.length()];
458 java.util.Arrays.fill(arr, ' ');
459 res = new StringBuilder(length).append(arr).append(s).toString();
460 }
461 return res;
462 }
463
464 /**
465 * Are the two String values the same.
466 * The Strings can be optionally trimmed before checking.
467 * The Strings can be optionally compared ignoring case.
468 * The Strings can be have embedded whitespace standardized before comparing.
469 * Two null values are treated as equal.
470 *
471 * @param s1 The first String.
472 * @param s2 The second String.
473 * @param trim Indicates if the Strings should be trimmed before comparison.
474 * @param ignoreCase Indicates if the case of the Strings should ignored during comparison.
475 * @param standardizeWhitespace Indicates if the embedded whitespace should be standardized before comparison.
476 * @return <code>true</code> if the Strings are the same, <code>false</code> otherwise.
477 */
478 @SuppressWarnings("PMD.CompareObjectsWithEquals")
479 public static boolean isSame(String s1, String s2, boolean trim, boolean ignoreCase, boolean standardizeWhitespace) {
480 if (s1 == s2) {
481 return true;
482 } else if (s1 == null || s2 == null) {
483 return false;
484 } else {
485 if (trim) {
486 s1 = s1.trim();
487 s2 = s2.trim();
488 }
489 if (standardizeWhitespace) {
490 // Replace all whitespace with a standard single space character.
491 s1 = s1.replaceAll("\\s+", " ");
492 s2 = s2.replaceAll("\\s+", " ");
493 }
494 return ignoreCase ? s1.equalsIgnoreCase(s2) : s1.equals(s2);
495 }
496 }
497
498 /**
499 * Formats all items onto a string with separators if more than one
500 * exists, return an empty string if the items are null or empty.
501 *
502 * @param items Object[]
503 * @param separator String
504 * @return String
505 */
506 public static String asString(Object[] items, String separator) {
507
508 if (items == null || items.length == 0) { return ""; }
509 if (items.length == 1) { return items[0].toString(); }
510
511 StringBuilder sb = new StringBuilder(items[0].toString());
512 for (int i=1; i<items.length; i++) {
513 sb.append(separator).append(items[i]);
514 }
515
516 return sb.toString();
517 }
518 }