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