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    }