View Javadoc

    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, '&', "&amp;");
    209         encoded = replaceString(encoded, '<', "&lt;");
    210         return replaceString(encoded, '>', "&gt;");
    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("&amp;");
    237             } else if (c == '"') {
    238                 buf.append("&quot;");
    239             } else if (c == '<') {
    240                 buf.append("&lt;");
    241             } else if (c == '>') {
    242                 buf.append("&gt;");
    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 }