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 }