001 package com.mockrunner.util.common; 002 003 import java.lang.reflect.Array; 004 import java.util.ArrayList; 005 import java.util.Collection; 006 import java.util.Iterator; 007 import java.util.List; 008 import java.util.Map; 009 010 import org.apache.commons.logging.Log; 011 import org.apache.commons.logging.LogFactory; 012 import org.apache.oro.text.regex.MalformedPatternException; 013 import org.apache.oro.text.regex.Pattern; 014 import org.apache.oro.text.regex.Perl5Compiler; 015 import org.apache.oro.text.regex.Perl5Matcher; 016 017 import com.mockrunner.base.NestedApplicationException; 018 019 /** 020 * Simple util class for <code>String</code> related methods. 021 */ 022 public class StringUtil 023 { 024 private final static Log log = LogFactory.getLog(StringUtil.class); 025 026 /** 027 * Replaces all occurrences of <code>match</code> in 028 * <code>source</code> with <code>replacement</code>. 029 * @param source the source string 030 * @param match the string that is searched 031 * @param replacement the replacement string 032 * @return the modified string 033 * @throws IllegalArgumentException if any argument is <code>null</code> or 034 * if <code>match</code> is the empty string 035 */ 036 public static String replaceAll(String source, String match, String replacement) 037 { 038 if(null == source || null == match || null == replacement) 039 { 040 throw new IllegalArgumentException("null strings not allowed"); 041 } 042 if(match.length() <= 0) 043 { 044 throw new IllegalArgumentException("match must not be empty"); 045 } 046 StringBuffer buffer = new StringBuffer(source.length() + 10); 047 int index = 0; 048 int newIndex = 0; 049 while((newIndex = source.indexOf(match, index)) >= 0) 050 { 051 buffer.append(source.substring(index, newIndex)); 052 buffer.append(replacement); 053 index = newIndex + match.length(); 054 } 055 buffer.append(source.substring(index)); 056 return buffer.toString(); 057 } 058 059 /** 060 * Compares two strings and returns the last 061 * index where the two string are equal. If 062 * the first characters of the two string do 063 * not match or if at least one of the two strings 064 * is empty, -1 is returned. 065 * @param string1 the first string 066 * @param string2 the second string 067 * @return the last index where the strings are equal 068 */ 069 public static int compare(String string1, String string2) 070 { 071 int endIndex = Math.min(string1.length(), string2.length()); 072 for(int ii = 0; ii < endIndex; ii++) 073 { 074 if(string1.charAt(ii) != string2.charAt(ii)) return ii - 1; 075 } 076 return endIndex - 1; 077 } 078 079 /** 080 * Converts the character at the specified index to 081 * lowercase and returns the resulting string. 082 * @param string the string to convert 083 * @param index the index where the character is set to lowercase 084 * @return the converted string 085 * @throws IndexOutOfBoundsException if the index is out of 086 * range 087 */ 088 public static String lowerCase(String string, int index) 089 { 090 return lowerCase(string, index, -1); 091 } 092 093 /** 094 * Converts the character in the specified index range to 095 * lowercase and returns the resulting string. 096 * If the provided endIndex is smaller or equal to startIndex, 097 * the endIndex is set to startIndex + 1. 098 * @param string the string to convert 099 * @param startIndex the index to start, inclusive 100 * @param endIndex the index to end, exclusive 101 * @return the converted string 102 * @throws IndexOutOfBoundsException if the index is out of 103 * range 104 */ 105 public static String lowerCase(String string, int startIndex, int endIndex) 106 { 107 StringBuffer buffer = new StringBuffer(string); 108 if(endIndex <= startIndex) endIndex = startIndex + 1; 109 for(int ii = startIndex; ii < endIndex; ii++) 110 { 111 char character = buffer.charAt(ii); 112 buffer.setCharAt(ii, Character.toLowerCase(character)); 113 } 114 return buffer.toString(); 115 } 116 117 /** 118 * Helper method for <code>toString()</code> implementations. 119 * Returns a string <code>"field name: value"</code>. Handles 120 * <code>null</code> values, collections and arrays. If the 121 * field is a collection or an array, the returned string will 122 * be:<br> 123 * <code>"field name 0: value0\nfield name 1: value1"</code> 124 * @param fieldName the field name 125 * @param field the field value 126 * @return a suitable string for <code>toString()</code> implementations 127 */ 128 public static String fieldToString(String fieldName, Object field) 129 { 130 StringBuffer buffer = new StringBuffer(); 131 if(null == field) 132 { 133 buffer.append(fieldName + ": " + "null"); 134 } 135 else if(field.getClass().isArray()) 136 { 137 arrayToString(fieldName, field, buffer); 138 } 139 else if(field instanceof Collection) 140 { 141 collectionToString(fieldName, field, buffer); 142 } 143 else if(field instanceof Map) 144 { 145 mapToString(fieldName, field, buffer); 146 } 147 else 148 { 149 buffer.append(fieldName + ": " + field.toString()); 150 } 151 return buffer.toString(); 152 } 153 154 private static void arrayToString(String fieldName, Object field, StringBuffer buffer) 155 { 156 int length = Array.getLength(field); 157 if(0 >= length) 158 { 159 buffer.append(fieldName + ": " + "empty"); 160 } 161 else 162 { 163 for(int ii = 0; ii < length; ii++) 164 { 165 buffer.append(fieldToString(fieldName + " " + ii, Array.get(field, ii))); 166 if(ii < length - 1) 167 { 168 buffer.append("\n"); 169 } 170 } 171 } 172 } 173 174 private static void collectionToString(String fieldName, Object field, StringBuffer buffer) 175 { 176 List list = new ArrayList((Collection)field); 177 if(0 >= list.size()) 178 { 179 buffer.append(fieldName + ": " + "empty"); 180 } 181 else 182 { 183 for(int ii = 0; ii < list.size(); ii++) 184 { 185 buffer.append(fieldToString(fieldName + " " + ii, list.get(ii))); 186 if(ii < list.size() - 1) 187 { 188 buffer.append("\n"); 189 } 190 } 191 } 192 } 193 194 private static void mapToString(String fieldName, Object field, StringBuffer buffer) 195 { 196 if(0 >= ((Map)field).size()) 197 { 198 buffer.append(fieldName + ": " + "empty"); 199 } 200 else 201 { 202 Iterator keys = ((Map)field).keySet().iterator(); 203 int ii = 0; 204 while(keys.hasNext()) 205 { 206 Object key = keys.next(); 207 Object value = ((Map)field).get(key); 208 buffer.append(fieldToString(fieldName + " " + key, value)); 209 if(ii < ((Map)field).size() - 1) 210 { 211 buffer.append("\n"); 212 } 213 ii++; 214 } 215 } 216 } 217 218 /** 219 * Appends the entries in the specified <code>List</code> as strings 220 * with a terminating <i>"\n"</i> after each row. 221 * @param buffer the buffer 222 * @param data the <code>List</code> with the data 223 */ 224 public static void appendObjectsAsString(StringBuffer buffer, List data) 225 { 226 for(int ii = 0; ii < data.size(); ii++) 227 { 228 buffer.append(data.get(ii)); 229 buffer.append("\n"); 230 } 231 } 232 233 /** 234 * Appends <i>number</i> tabs (\t) to the buffer. 235 * @param buffer the buffer 236 * @param number the number of tabs to append 237 */ 238 public static void appendTabs(StringBuffer buffer, int number) 239 { 240 for(int ii = 0; ii < number; ii++) 241 { 242 buffer.append("\t"); 243 } 244 } 245 246 /** 247 * Splits a string into tokens. Similar to <code>StringTokenizer</code> 248 * except that empty tokens are recognized and added as <code>null</code>. 249 * With a delimiter of <i>";"</i> the string 250 * <i>"a;;b;c;;"</i> will split into 251 * <i>["a"] [null] ["b"] ["c"] [null]</i>. 252 * @param string the String 253 * @param delim the delimiter 254 * @param doTrim should each token be trimmed 255 * @return the array of tokens 256 */ 257 public static String[] split(String string, String delim, boolean doTrim) 258 { 259 int pos = 0, begin = 0; 260 ArrayList resultList = new ArrayList(); 261 while((-1 != (pos = string.indexOf(delim, begin))) && (begin < string.length())) 262 { 263 String token = string.substring(begin, pos); 264 if(doTrim) token = token.trim(); 265 if(token.length() == 0) token = null; 266 resultList.add(token); 267 begin = pos + delim.length(); 268 } 269 if(begin < string.length()) 270 { 271 String token = string.substring(begin); 272 if(doTrim) token = token.trim(); 273 if(token.length() == 0) token = null; 274 resultList.add(token); 275 } 276 return (String[])resultList.toArray(new String[resultList.size()]); 277 } 278 279 /** 280 * Returns how many times <code>string</code> contains 281 * <code>other</code>. 282 * @param string the string to search 283 * @param other the string that is searched 284 * @return the number of occurences 285 */ 286 public static int countMatches(String string, String other) 287 { 288 if(null == string) return 0; 289 if(null == other) return 0; 290 if(0 >= string.length()) return 0; 291 if(0 >= other.length()) return 0; 292 int count = 0; 293 int index = 0; 294 while((index <= string.length() - other.length()) && (-1 != (index = string.indexOf(other, index)))) 295 { 296 count++; 297 index += other.length(); 298 } 299 return count; 300 } 301 302 303 /** 304 * Returns if the specified strings are equal, ignoring 305 * case, if <code>caseSensitive</code> is <code>false</code>. 306 * @param source the source String 307 * @param target the target String 308 * @param caseSensitive is the comparison case sensitive 309 * @return <code>true</code> if the strings matches 310 * <code>false</code> otherwise 311 */ 312 public static boolean matchesExact(String source, String target, boolean caseSensitive) 313 { 314 if(!caseSensitive) 315 { 316 source = source.toLowerCase(); 317 target = target.toLowerCase(); 318 } 319 return (source.equals(target)); 320 } 321 322 /** 323 * Returns if <code>source</code> contains <code>target</code>, 324 * ignoring case, if <code>caseSensitive</code> is <code>false</code>. 325 * @param source the source String 326 * @param target the target String 327 * @param caseSensitive is the comparison case sensitive 328 * @return <code>true</code> if the strings matches 329 * <code>false</code> otherwise 330 */ 331 public static boolean matchesContains(String source, String target, boolean caseSensitive) 332 { 333 if(!caseSensitive) 334 { 335 source = source.toLowerCase(); 336 target = target.toLowerCase(); 337 } 338 return (-1 != source.indexOf(target)); 339 } 340 341 /** 342 * Returns if the regular expression <code>target</code> matches 343 * <code>source</code>, ignoring case, if <code>caseSensitive</code> 344 * is <code>false</code>. 345 * @param source the source String 346 * @param target the target String 347 * @param caseSensitive is the comparison case sensitive 348 * @return <code>true</code> if the strings matches 349 * <code>false</code> otherwise 350 */ 351 public static boolean matchesPerl5(String source, String target, boolean caseSensitive) 352 { 353 int mask = Perl5Compiler.CASE_INSENSITIVE_MASK; 354 if(caseSensitive) 355 { 356 mask = Perl5Compiler.DEFAULT_MASK; 357 } 358 try 359 { 360 Pattern pattern = new Perl5Compiler().compile(target, mask); 361 return (new Perl5Matcher().matches(source, pattern)); 362 } 363 catch(MalformedPatternException exc) 364 { 365 log.error("Malformed pattern", exc); 366 throw new NestedApplicationException(exc); 367 } 368 } 369 }