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    }