View Javadoc
1   /**
2    * 
3    */
4   package edu.internet2.middleware.grouperActivemq.utils;
5   
6   import java.io.IOException;
7   import java.io.PrintWriter;
8   import java.io.StringWriter;
9   import java.io.UnsupportedEncodingException;
10  import java.io.Writer;
11  import java.lang.reflect.Array;
12  import java.lang.reflect.Field;
13  import java.lang.reflect.InvocationTargetException;
14  import java.lang.reflect.Method;
15  import java.math.BigDecimal;
16  import java.net.URLDecoder;
17  import java.net.URLEncoder;
18  import java.security.MessageDigest;
19  import java.security.NoSuchAlgorithmException;
20  import java.sql.SQLException;
21  import java.sql.Timestamp;
22  import java.text.DecimalFormat;
23  import java.text.ParseException;
24  import java.text.SimpleDateFormat;
25  import java.util.ArrayList;
26  import java.util.Calendar;
27  import java.util.Collection;
28  import java.util.Date;
29  import java.util.GregorianCalendar;
30  import java.util.HashMap;
31  import java.util.HashSet;
32  import java.util.Iterator;
33  import java.util.LinkedHashMap;
34  import java.util.LinkedHashSet;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.Set;
38  import java.util.regex.Matcher;
39  import java.util.regex.Pattern;
40  
41  import edu.internet2.middleware.grouperClient.util.GrouperClientUtils;
42  import edu.internet2.middleware.grouperClientExt.org.apache.commons.codec.binary.Base64;
43  import edu.internet2.middleware.grouperClientExt.org.apache.commons.logging.Log;
44  
45  /**
46   * utility methods for grouper that are safe to use in expression language.  Generally these are static methods.
47   * 
48   * @author mchyzer
49   *
50   */
51  public class GcElUtilsSafe {
52  
53    /**
54     * append objects since there is no way to do this in EL
55     * @param objects inputs to append to each other
56     * @return the string appended
57     */
58    public static String append(Object... objects) {
59      
60      if (objects == null) {
61        return null;
62      }
63      
64      if (objects.length == 0) {
65        return stringValue(objects[0]);
66      }
67      
68      StringBuilder result = new StringBuilder();
69      
70      for (Object object : objects) {
71        result.append(stringValue(object));
72      }
73      
74      return result.toString();
75    }
76    
77    /**
78     * take email addresses from a textarea and turn them into semi separated
79     * @param emailAddresses can be whitespace, comma, or semi separated
80     * @return the email addresses semi separated
81     */
82    public static String normalizeEmailAddresses(String emailAddresses) {
83      if (emailAddresses == null) {
84        return null;
85      }
86      emailAddresses = replace(emailAddresses, ",", " ");
87      emailAddresses = replace(emailAddresses, ";", " ");
88      emailAddresses = replace(emailAddresses, "\n", " ");
89      emailAddresses = replace(emailAddresses, "\t", " ");
90      emailAddresses = replace(emailAddresses, "\r", " ");
91      emailAddresses = join(splitTrim(emailAddresses, " "), ";");
92      return emailAddresses;
93    }
94  
95    /** 
96     * pattern as simple validation for email.  need text, @ sign, then text, dot, and text.
97     * granted this could be better, but this is a first step
98     */
99    private static Pattern emailPattern = Pattern.compile("^[^@]+@[^.]+\\..+$");
100 
101   /**
102    * 
103    * @param email
104    * @return true if valid, false if not
105    */
106   public static boolean validEmail(String email) {
107     Matcher matcher = emailPattern.matcher(email);
108     return matcher.matches();
109   }
110 
111   /**
112    * shorten a set if it is too long
113    * @param <T>
114    * @param theSet
115    * @param maxSize
116    * @return the new set
117    */
118   public static <T> Set<T> setShorten(Set<T> theSet, int maxSize) {
119 
120     if (length(theSet) < maxSize) {
121       return theSet;
122     }
123 
124     //truncate the list
125     Set<T> newList = new LinkedHashSet<T>();
126     int i = 0;
127 
128     //TODO test this logic
129     for (T t : theSet) {
130 
131       if (i >= maxSize) {
132         break;
133       }
134 
135       newList.add(t);
136       i++;
137     }
138     return newList;
139   }
140 
141   /**
142    * 
143    * @param number e.g. 12345678
144    * @return the string, e.g. 12,345,678
145    */
146   public static String formatNumberWithCommas(Long number) {
147     if (number == null) {
148       return "null";
149     }
150     DecimalFormat df = new DecimalFormat();
151     return df.format(number);
152   }
153 
154   /**
155    * compare null safe
156    * @param first
157    * @param second
158    * @return 0 for equal, 1 for greater, -1 for less
159    */
160   @SuppressWarnings("unchecked")
161   public static int compare(Comparable first, Comparable second) {
162     if (first == second) {
163       return 0;
164     }
165     if (first == null) {
166       return -1;
167     }
168     if (second == null) {
169       return 1;
170     }
171     return first.compareTo(second);
172   }
173 
174   /**
175    * turn some strings into a map
176    * @param strings
177    * @return the map (never null)
178    */
179   public static Map<String, String> toMap(String... strings) {
180     Map<String, String> map = new LinkedHashMap<String, String>();
181     if (strings != null) {
182       if (strings.length % 2 != 0) {
183         throw new RuntimeException("Must pass in an even number of strings: "
184             + strings.length);
185       }
186       for (int i = 0; i < strings.length; i += 2) {
187         map.put(strings[i], strings[i + 1]);
188       }
189     }
190     return map;
191   }
192 
193   /**
194    * turn some strings into a map
195    * @param stringObjects is an array of String,Object,String,Object etc where the 
196    * Strings are the key, and the Object is the value
197    * @return the map (never null)
198    */
199   public static Map<String, Object> toStringObjectMap(Object... stringObjects) {
200     Map<String, Object> map = new LinkedHashMap<String, Object>();
201     if (stringObjects != null) {
202       if (stringObjects.length % 2 != 0) {
203         throw new RuntimeException("Must pass in an even number of strings: "
204             + stringObjects.length);
205       }
206       for (int i = 0; i < stringObjects.length; i += 2) {
207         String key = (String) stringObjects[i];
208         map.put(key, stringObjects[i + 1]);
209       }
210     }
211     return map;
212   }
213 
214   /**
215    * convert millis to friendly string
216    * @param duration
217    * @return the friendly string
218    */
219   public static String convertMillisToFriendlyString(Integer duration) {
220     if (duration == null) {
221       return convertMillisToFriendlyString((Long) null);
222     }
223     return convertMillisToFriendlyString(new Long(duration.intValue()));
224   }
225 
226   /**
227    * convert millis to friendly string
228    * @param duration
229    * @return the friendly string
230    */
231   public static String convertMillisToFriendlyString(Long duration) {
232 
233     if (duration == null) {
234       return "";
235     }
236 
237     if (duration < 1000) {
238       return duration + "ms";
239     }
240 
241     long ms = duration % 1000;
242     duration = duration / 1000;
243     long s = duration % 60;
244     duration = duration / 60;
245 
246     if (duration == 0) {
247       return s + "s, " + ms + "ms";
248     }
249 
250     long m = duration % 60;
251     duration = duration / 60;
252 
253     if (duration == 0) {
254       return m + "m, " + s + "s, " + ms + "ms";
255     }
256 
257     long h = duration % 24;
258     duration = duration / 24;
259 
260     if (duration == 0) {
261       return h + "h, " + m + "m, " + s + "s, " + ms + "ms";
262     }
263 
264     long d = duration;
265 
266     return d + "d, " + h + "h, " + m + "m, " + s + "s, " + ms + "ms";
267   }
268 
269   /**
270    * return the arg after the argBefore, or null if not there, or exception
271    * if argBefore is not found
272    * @param args
273    * @param argBefore
274    * @return the arg
275    */
276   public static String argAfter(String[] args, String argBefore) {
277     if (length(args) <= 1) {
278       return null;
279     }
280     int argBeforeIndex = -1;
281     for (int i = 0; i < args.length; i++) {
282       if (equals(args[i], argBefore)) {
283         argBeforeIndex = i;
284         break;
285       }
286     }
287     if (argBeforeIndex == -1) {
288       throw new RuntimeException("Cant find arg before");
289     }
290     if (argBeforeIndex < args.length - 1) {
291       return args[argBeforeIndex + 1];
292     }
293     return null;
294   }
295 
296   /**
297    * append and maybe put a separator in there
298    * @param result
299    * @param separatorIfResultNotEmpty
300    * @param stringToAppend
301    */
302   public static void append(StringBuilder result,
303       String separatorIfResultNotEmpty, String stringToAppend) {
304     if (result.length() != 0) {
305       result.append(separatorIfResultNotEmpty);
306     }
307     result.append(stringToAppend);
308   }
309 
310   /**
311    * 
312    */
313   public static final String LOG_ERROR = "Error trying to make parent dirs for logger or logging first statement, check to make "
314       +
315                 "sure you have proper file permissions, and that your servlet container is giving "
316       +
317                 "your app rights to access the log directory (e.g. for tomcat set TOMCAT5_SECURITY=no), g"
318       +
319                 "oogle it for more info";
320 
321   /**
322    * The number of bytes in a kilobyte.
323    */
324   public static final long ONE_KB = 1024;
325 
326   /**
327    * The number of bytes in a megabyte.
328    */
329   public static final long ONE_MB = ONE_KB * ONE_KB;
330 
331   /**
332    * The number of bytes in a gigabyte.
333    */
334   public static final long ONE_GB = ONE_KB * ONE_MB;
335 
336   /**
337    * Returns a human-readable version of the file size (original is in
338    * bytes).
339    *
340    * @param size The number of bytes.
341    * @return     A human-readable display value (includes units).
342    * @todo need for I18N?
343    */
344   public static String byteCountToDisplaySize(long size) {
345     String displaySize;
346 
347     if (size / ONE_GB > 0) {
348       displaySize = String.valueOf(size / ONE_GB) + " GB";
349     } else if (size / ONE_MB > 0) {
350       displaySize = String.valueOf(size / ONE_MB) + " MB";
351     } else if (size / ONE_KB > 0) {
352       displaySize = String.valueOf(size / ONE_KB) + " KB";
353     } else {
354       displaySize = String.valueOf(size) + " bytes";
355     }
356 
357     return displaySize;
358   }
359 
360   /**
361    * return the suffix after a char.  If the char doesnt exist, just return the string
362    * @param input string
363    * @param theChar char
364    * @return new string
365    */
366   public static String suffixAfterChar(String input, char theChar) {
367     if (input == null) {
368       return null;
369     }
370     //get the real type off the end
371     int lastIndex = input.lastIndexOf(theChar);
372     if (lastIndex > -1) {
373       input = input.substring(lastIndex + 1, input.length());
374     }
375     return input;
376   }
377 
378   /**
379    * sleep, if interrupted, throw runtime
380    * @param millis
381    */
382   public static void sleep(long millis) {
383     try {
384       Thread.sleep(millis);
385     } catch (InterruptedException ie) {
386       throw new RuntimeException(ie);
387     }
388   }
389 
390   /**
391    * 
392    * @param seconds
393    */
394   public static void sleepWithStdoutCountdown(int seconds) {
395     for (int i = seconds; i > 0; i--) {
396       System.out.println("Sleeping: " + i);
397       sleep(1000);
398     }
399   }
400 
401   /**
402    * encrypt a message to SHA
403    * @param plaintext
404    * @return the hash
405    */
406   public synchronized static String encryptSha(String plaintext) {
407     MessageDigest md = null;
408     try {
409       md = MessageDigest.getInstance("SHA"); //step 2
410     } catch (NoSuchAlgorithmException e) {
411       throw new RuntimeException(e);
412     }
413     try {
414       md.update(plaintext.getBytes("UTF-8")); //step 3
415     } catch (UnsupportedEncodingException e) {
416       throw new RuntimeException(e);
417     }
418     byte raw[] = md.digest(); //step 4
419     byte[] encoded = Base64.encodeBase64(raw); //step 5
420     String hash = new String(encoded);
421     //String hash = (new BASE64Encoder()).encode(raw); //step 5
422     return hash; //step 6
423   }
424 
425   /**
426    * get a unique string identifier based on the current time,
427    * this is not globally unique, just unique for as long as this
428    * server is running...
429    * 
430    * @return String
431    */
432   public static String uniqueId() {
433     //this needs to be threadsafe since we are using a static field
434     synchronized (GcElUtilsSafe.class) {
435       lastId = incrementStringInt(lastId);
436     }
437 
438     return String.valueOf(lastId);
439   }
440 
441   /**
442    * make sure a array is non null.  If null, then return an empty array.
443    * @param <T>
444    * @param array
445    * @param theClass to make array from
446    * @return the list or empty list if null
447    */
448   @SuppressWarnings( { "unchecked", "cast" })
449   public static <T> T[] nonNull(T[] array, Class<?> theClass) {
450     if (int.class.equals(theClass)) {
451       return (T[]) (Object) new int[0];
452     }
453     if (float.class.equals(theClass)) {
454       return (T[]) (Object) new float[0];
455     }
456     if (double.class.equals(theClass)) {
457       return (T[]) (Object) new double[0];
458     }
459     if (short.class.equals(theClass)) {
460       return (T[]) (Object) new short[0];
461     }
462     if (long.class.equals(theClass)) {
463       return (T[]) (Object) new long[0];
464     }
465     if (byte.class.equals(theClass)) {
466       return (T[]) (Object) new byte[0];
467     }
468     if (boolean.class.equals(theClass)) {
469       return (T[]) (Object) new boolean[0];
470     }
471     if (char.class.equals(theClass)) {
472       return (T[]) (Object) new char[0];
473     }
474     return array == null ? ((T[]) Array.newInstance(theClass, 0)) : array;
475   }
476 
477   /**
478    * strip the suffix off
479    * @param string
480    * @param suffix
481    * @return the string without the suffix
482    */
483   public static String stripSuffix(String string, String suffix) {
484     if (string == null || suffix == null) {
485       return string;
486     }
487     if (string.endsWith(suffix)) {
488       return string.substring(0, string.length() - suffix.length());
489     }
490     return string;
491   }
492 
493   /**
494    * get the prefix or suffix of a string based on a separator
495    * 
496    * @param startString
497    *          is the string to start with
498    * @param separator
499    *          is the separator to split on
500    * @param isPrefix
501    *          if thre prefix or suffix should be returned
502    * 
503    * @return the prefix or suffix, if the separator isnt there, return the
504    *         original string
505    */
506   public static String prefixOrSuffix(String startString, String separator,
507       boolean isPrefix) {
508     String prefixOrSuffix = null;
509 
510     //no nulls
511     if (startString == null) {
512       return startString;
513     }
514 
515     //where is the separator
516     int separatorIndex = startString.indexOf(separator);
517 
518     //if none exists, dont proceed
519     if (separatorIndex == -1) {
520       return startString;
521     }
522 
523     //maybe the separator isnt on character
524     int separatorLength = separator.length();
525 
526     if (isPrefix) {
527       prefixOrSuffix = startString.substring(0, separatorIndex);
528     } else {
529       prefixOrSuffix = startString.substring(separatorIndex + separatorLength,
530           startString.length());
531     }
532 
533     return prefixOrSuffix;
534   }
535 
536 
537   /**
538    * get the extension from name.  if name is a:b:c, name is c
539    * @param name
540    * @return the name
541    */
542   public static String extensionFromName(String name) {
543     if (isBlank(name)) {
544       return name;
545     }
546     int lastColonIndex = name.lastIndexOf(':');
547     if (lastColonIndex == -1) {
548       return name;
549     }
550     String extension = name.substring(lastColonIndex + 1);
551     return extension;
552   }
553 
554   /**
555    * get the parent stem name from name.  if already a root stem
556    * then just return null.  e.g. if the name is a:b:c then
557    * the return value is a:b
558    * @param name
559    * @return the parent stem name or null if none
560    */
561   public static String parentStemNameFromName(String name) {
562     return parentStemNameFromName(name, true);
563   }
564 
565   /**
566    * get the parent stem name from name.  if already a root stem
567    * then just return null.  e.g. if the name is a:b:c then
568    * the return value is a:b
569    * @param name
570    * @param nullForRoot null for root, otherwise colon
571    * @return the parent stem name or null if none
572    */
573   public static String parentStemNameFromName(String name, boolean nullForRoot) {
574 
575     //null safe
576     if (isBlank(name)) {
577       return name;
578     }
579 
580     int lastColonIndex = name.lastIndexOf(':');
581     if (lastColonIndex == -1) {
582 
583       if (nullForRoot) {
584         return null;
585       }
586       return ":";
587     }
588     String parentStemName = name.substring(0, lastColonIndex);
589     return parentStemName;
590 
591   }
592 
593   /**
594    * return the string or the other if the first is blank
595    * @param string
596    * @param defaultStringIfBlank
597    * @return the string or the default one
598    */
599   public static String defaultIfBlank(String string, String defaultStringIfBlank) {
600     return isBlank(string) ? defaultStringIfBlank : string;
601   }
602 
603   /**
604    * genericized method to see if first is null, if so then return second, else first.
605    * @param <T>
606    * @param theValue first input
607    * @param defaultIfTheValueIsNull second input
608    * @return the first if not null, second if no
609    */
610   public static <T> T defaultIfNull(T theValue, T defaultIfTheValueIsNull) {
611     return theValue != null ? theValue : defaultIfTheValueIsNull;
612   }
613 
614   /**
615    * add each element of listToAdd if it is not already in list
616    * @param <T>
617    * @param list to add to
618    * @param listToAdd each element will be added to list, or null if none
619    */
620   public static <T> void addIfNotThere(Collection<T> list, Collection<T> listToAdd) {
621     //maybe nothing to do
622     if (listToAdd == null) {
623       return;
624     }
625     for (T t : listToAdd) {
626       if (!list.contains(t)) {
627         list.add(t);
628       }
629     }
630   }
631 
632   /**
633    * print out various types of objects
634    * 
635    * @param object
636    * @param maxChars is where it should stop when figuring out object.  note, result might be longer than max...
637    * need to abbreviate when back
638    * @param result is where to append to
639    */
640   @SuppressWarnings("unchecked")
641   private static void toStringForLogHelper(Object object, int maxChars,
642       StringBuilder result) {
643 
644     try {
645       if (object == null) {
646         result.append("null");
647       } else if (object.getClass().isArray()) {
648         // handle arrays
649         int length = Array.getLength(object);
650         if (length == 0) {
651           result.append("Empty array");
652         } else {
653           result.append("Array size: ").append(length).append(": ");
654           for (int i = 0; i < length; i++) {
655             result.append("[").append(i).append("]: ").append(
656                 Array.get(object, i)).append("\n");
657             if (maxChars != -1 && result.length() > maxChars) {
658               return;
659             }
660           }
661         }
662       } else if (object instanceof Collection) {
663         //give size and type if collection
664         Collection<Object> collection = (Collection<Object>) object;
665         int collectionSize = collection.size();
666         if (collectionSize == 0) {
667           result.append("Empty ").append(object.getClass().getSimpleName());
668         } else {
669           result.append(object.getClass().getSimpleName()).append(" size: ").append(
670               collectionSize).append(": ");
671           int i = 0;
672           for (Object collectionObject : collection) {
673             result.append("[").append(i).append("]: ").append(
674                 collectionObject).append("\n");
675             if (maxChars != -1 && result.length() > maxChars) {
676               return;
677             }
678             i++;
679           }
680         }
681       } else {
682         result.append(object.toString());
683       }
684     } catch (Exception e) {
685       result.append("<<exception>> ").append(object.getClass()).append(":\n")
686           .append(getFullStackTrace(e)).append("\n");
687     }
688   }
689 
690   /**
691    * convert a set to a string (comma separate)
692    * @param collection
693    * @return the String
694    */
695   public static String collectionToString(Collection collection) {
696     if (collection == null) {
697       return "null";
698     }
699     if (collection.size() == 0) {
700       return "empty";
701     }
702     StringBuilder result = new StringBuilder();
703     boolean first = true;
704     for (Object object : collection) {
705       if (!first) {
706         result.append(", ");
707       }
708       first = false;
709       result.append(object);
710     }
711     return result.toString();
712 
713   }
714 
715   /**
716    * convert a set to a string (comma separate)
717    * @param set
718    * @return the String
719    */
720   public static String setToString(Set set) {
721     return collectionToString(set);
722   }
723 
724   /**
725    * convert a set to a string (comma separate)
726    * @param map
727    * @return the String
728    * @deprecated use mapToString(map)
729    */
730   @Deprecated
731   public static String MapToString(Map map) {
732     return mapToString(map);
733   }
734 
735   /**
736    * convert a set to a string (comma separate)
737    * @param map
738    * @return the String
739    */
740   public static String mapToString(Map map) {
741     if (map == null) {
742       return "null";
743     }
744     if (map.size() == 0) {
745       return "empty";
746     }
747     StringBuilder result = new StringBuilder();
748     boolean first = true;
749     for (Object object : map.keySet()) {
750       if (!first) {
751         result.append(", ");
752       }
753       first = false;
754       result.append(object).append(": ").append(map.get(object));
755     }
756     return result.toString();
757   }
758 
759   /**
760    * print out various types of objects
761    * 
762    * @param object
763    * @return the string value
764    */
765   public static String toStringForLog(Object object) {
766     StringBuilder result = new StringBuilder();
767     toStringForLogHelper(object, -1, result);
768     return result.toString();
769   }
770 
771   /**
772    * print out various types of objects
773    * 
774    * @param object
775    * @param maxChars is the max chars that should be returned (abbreviate if longer), or -1 for any amount
776    * @return the string value
777    */
778   public static String toStringForLog(Object object, int maxChars) {
779     StringBuilder result = new StringBuilder();
780     toStringForLogHelper(object, -1, result);
781     String resultString = result.toString();
782     if (maxChars != -1) {
783       return abbreviate(resultString, maxChars);
784     }
785     return resultString;
786   }
787 
788   /**
789    * If batching this is the number of batches
790    * @param count is size of set
791    * @param batchSize
792    * @return the number of batches
793    */
794   public static int batchNumberOfBatches(int count, int batchSize) {
795     //not sure why this would be 0...
796     if (batchSize == 0) {
797       return 0;
798     }
799     int batches = 1 + ((count - 1) / batchSize);
800     return batches;
801 
802   }
803 
804   /**
805    * If batching this is the number of batches
806    * @param collection
807    * @param batchSize
808    * @return the number of batches
809    */
810   public static int batchNumberOfBatches(Collection<?> collection, int batchSize) {
811     int arrraySize = length(collection);
812     return batchNumberOfBatches(arrraySize, batchSize);
813 
814   }
815 
816   /**
817    * retrieve a batch by 0 index. Will return an array of size batchSize or
818    * the remainder. the array will be full of elements. Note, this requires an
819    * ordered input (so use linkedhashset not hashset if doing sets)
820    * @param <T> template type
821    * @param collection
822    * @param batchSize
823    * @param batchIndex
824    * @return the list
825    *         This never returns null, only empty list
826    */
827   @SuppressWarnings("unchecked")
828   public static <T> List<T> batchList(Collection<T> collection, int batchSize,
829       int batchIndex) {
830 
831     int numberOfBatches = batchNumberOfBatches(collection, batchSize);
832     int arraySize = length(collection);
833 
834     // short circuit
835     if (arraySize == 0) {
836       return new ArrayList<T>();
837     }
838 
839     List<T> theBatchObjects = new ArrayList<T>();
840 
841     // lets get the type of the first element if possible
842     //    Object first = get(arrayOrCollection, 0);
843     //
844     //    Class theType = first == null ? Object.class : first.getClass();
845 
846     // if last batch
847     if (batchIndex == numberOfBatches - 1) {
848 
849       // needs to work to 1-n
850       //int thisBatchSize = 1 + ((arraySize - 1) % batchSize);
851 
852       int collectionIndex = 0;
853       for (T t : collection) {
854         if (collectionIndex++ < batchIndex * batchSize) {
855           continue;
856         }
857         //just copy the rest
858         //if (collectionIndex >= (batchIndex * batchSize) + arraySize) {
859         //  break;
860         //}
861         //we are in the copy mode
862         theBatchObjects.add(t);
863       }
864 
865     } else {
866       // if non-last batch
867       //int newIndex = 0;
868       int collectionIndex = 0;
869       for (T t : collection) {
870         if (collectionIndex < batchIndex * batchSize) {
871           collectionIndex++;
872           continue;
873         }
874         //done with batch
875         if (collectionIndex >= (batchIndex + 1) * batchSize) {
876           break;
877         }
878         theBatchObjects.add(t);
879         collectionIndex++;
880       }
881     }
882     return theBatchObjects;
883   }
884 
885   /**
886    * split a string based on a separator into an array, and trim each entry (see
887    * the Commons Util trim() for more details)
888    * 
889    * @param input
890    *          is the delimited input to split and trim
891    * @param separator
892    *          is what to split on
893    * 
894    * @return the array of items after split and trimmed, or null if input is null.  will be trimmed to empty
895    */
896   public static String[] splitTrim(String input, String separator) {
897     return splitTrim(input, separator, true);
898   }
899 
900   /**
901    * split a string based on a separator into an array, and trim each entry (see
902    * the Commons Util trim() for more details)
903    * 
904    * @param input
905    *          is the delimited input to split and trim
906    * @param separator
907    *          is what to split on
908    * 
909    * @return the list of items after split and trimmed, or null if input is null.  will be trimmed to empty
910    */
911   public static List<String> splitTrimToList(String input, String separator) {
912     if (isBlank(input)) {
913       return null;
914     }
915     String[] array = splitTrim(input, separator);
916     return toList(array);
917   }
918 
919   /**
920    * split a string based on a separator into an array, and trim each entry (see
921    * the Commons Util trim() for more details)
922    * 
923    * @param input
924    *          is the delimited input to split and trim
925    * @param separator
926    *          is what to split on
927    * 
928    * @return the set of items after split and trimmed, or null if input is null.  will be trimmed to empty
929    */
930   public static Set<String> splitTrimToSet(String input, String separator) {
931     if (isBlank(input)) {
932       return null;
933     }
934     String[] array = splitTrim(input, separator);
935     return toSet(array);
936   }
937 
938   /**
939    * split a string based on a separator into an array, and trim each entry (see
940    * the Commons Util trim() for more details)
941    * 
942    * @param input
943    *          is the delimited input to split and trim
944    * @param separator
945    *          is what to split on
946    * @param treatAdjacentSeparatorsAsOne
947    * @return the array of items after split and trimmed, or null if input is null.  will be trimmed to empty
948    */
949   public static String[] splitTrim(String input, String separator,
950       boolean treatAdjacentSeparatorsAsOne) {
951     if (isBlank(input)) {
952       return null;
953     }
954 
955     //first split
956     String[] items = treatAdjacentSeparatorsAsOne ? splitByWholeSeparator(input,
957         separator) :
958         split(input, separator);
959 
960     //then trim
961     for (int i = 0; (items != null) && (i < items.length); i++) {
962       items[i] = trim(items[i]);
963     }
964 
965     //return the array
966     return items;
967   }
968 
969   /**
970    * escape url chars (e.g. a # is %23)
971    * @param string input
972    * @return the encoded string
973    */
974   public static String escapeUrlEncode(String string) {
975     String result = null;
976     try {
977       result = URLEncoder.encode(string, "UTF-8");
978     } catch (UnsupportedEncodingException ex) {
979       throw new RuntimeException("UTF-8 not supported", ex);
980     }
981     return result;
982   }
983 
984   /**
985    * unescape url chars (e.g. a space is %20)
986    * @param string input
987    * @return the encoded string
988    */
989   public static String escapeUrlDecode(String string) {
990     String result = null;
991     try {
992       result = URLDecoder.decode(string, "UTF-8");
993     } catch (UnsupportedEncodingException ex) {
994       throw new RuntimeException("UTF-8 not supported", ex);
995     }
996     return result;
997   }
998 
999   /**
1000    * make sure a list is non null.  If null, then return an empty list
1001    * @param <T>
1002    * @param list
1003    * @return the list or empty list if null
1004    */
1005   public static <T> List<T> nonNull(List<T> list) {
1006     return list == null ? new ArrayList<T>() : list;
1007   }
1008 
1009   /**
1010    * make sure a collection is non null.  If null, then return an empty list
1011    * @param <T>
1012    * @param list
1013    * @return the list or empty list if null
1014    */
1015   public static <T> Collection<T> nonNull(Collection<T> list) {
1016     return list == null ? new ArrayList<T>() : list;
1017   }
1018 
1019   /**
1020    * make sure a list is non null.  If null, then return an empty set
1021    * @param <T>
1022    * @param set
1023    * @return the set or empty set if null
1024    */
1025   public static <T> Set<T> nonNull(Set<T> set) {
1026     return set == null ? new HashSet<T>() : set;
1027   }
1028 
1029   /**
1030    * make sure it is non null, if null, then give new map
1031    * 
1032    * @param <K> key of map
1033    * @param <V> value of map
1034    * @param map is map
1035    * @return set non null
1036    */
1037   public static <K, V> Map<K, V> nonNull(Map<K, V> map) {
1038     return map == null ? new HashMap<K, V>() : map;
1039   }
1040 
1041   /**
1042    * return a list of objects from varargs.  Though if there is one
1043    * object, and it is a list, return it.
1044    * 
1045    * @param <T>
1046    *            template type of the objects
1047    * @param objects
1048    * @return the list or null if objects is null
1049    */
1050   @SuppressWarnings("unchecked")
1051   public static <T> List<T> toList(T... objects) {
1052     if (objects == null) {
1053       return null;
1054     }
1055     if (objects.length == 1 && objects[0] instanceof List) {
1056       return (List<T>) objects[0];
1057     }
1058 
1059     List<T> result = new ArrayList<T>();
1060     for (T object : objects) {
1061       result.add(object);
1062     }
1063     return result;
1064   }
1065 
1066   /**
1067    * return a list of objects from varargs.  Though if there is one
1068    * object, and it is a list, return it.
1069    * 
1070    * @param objects
1071    * @return the list or null if objects is null
1072    */
1073   public static List<Object> toListObject(Object... objects) {
1074     if (objects == null) {
1075       return null;
1076     }
1077     List<Object> result = new ArrayList<Object>();
1078     for (Object object : objects) {
1079       result.add(object);
1080     }
1081     return result;
1082   }
1083 
1084   /**
1085    * return a set of objects from varargs.
1086    * 
1087    * @param <T> template type of the objects
1088    * @param objects
1089    * @return the set
1090    */
1091   public static <T> Set<T> toSet(T... objects) {
1092     if (objects == null) {
1093       return null;
1094     }
1095     Set<T> result = new LinkedHashSet<T>();
1096     for (T object : objects) {
1097       result.add(object);
1098     }
1099     return result;
1100   }
1101 
1102   /**
1103    * return a set of string
1104    * 
1105    * @param <T> template type of the objects
1106    * @param object
1107    * @return the set
1108    */
1109   public static <T> Set<T> toSetObject(T object) {
1110     if (object == null) {
1111       return null;
1112     }
1113     Set<T> result = new LinkedHashSet<T>();
1114     result.add(object);
1115     return result;
1116   }
1117 
1118   /**
1119    * string format of dates
1120    */
1121   public static final String DATE_FORMAT = "yyyyMMdd";
1122 
1123   /**
1124    * string format of dates for file names
1125    */
1126   public static final String TIMESTAMP_FILE_FORMAT = "yyyy_MM_dd__HH_mm_ss_SSS";
1127 
1128   /**
1129    * timestamp format, make sure to synchronize
1130    */
1131   final static SimpleDateFormat timestampFileFormat = new SimpleDateFormat(
1132       TIMESTAMP_FILE_FORMAT);
1133 
1134   /**
1135    * string format of dates
1136    */
1137   public static final String DATE_FORMAT2 = "yyyy/MM/dd";
1138 
1139   /**
1140    * format including minutes and seconds: yyyy/MM/dd HH:mm:ss
1141    */
1142   public static final String DATE_MINUTES_SECONDS_FORMAT = "yyyy/MM/dd HH:mm:ss";
1143 
1144   /**
1145    * format including minutes and seconds: yyyyMMdd HH:mm:ss
1146    */
1147   public static final String DATE_MINUTES_SECONDS_NO_SLASH_FORMAT = "yyyyMMdd HH:mm:ss";
1148 
1149   /**
1150    * format on screen of config for milestone: yyyy/MM/dd HH:mm:ss.SSS
1151    */
1152   public static final String TIMESTAMP_FORMAT = "yyyy/MM/dd HH:mm:ss.SSS";
1153 
1154   /**
1155    * format on screen of config for milestone: yyyyMMdd HH:mm:ss.SSS
1156    */
1157   public static final String TIMESTAMP_NO_SLASH_FORMAT = "yyyyMMdd HH:mm:ss.SSS";
1158 
1159   /**
1160    * date format, make sure to synchronize
1161    */
1162   final static SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
1163 
1164   /**
1165    * date format, make sure to synchronize
1166    */
1167   final static SimpleDateFormat dateFormat2 = new SimpleDateFormat(DATE_FORMAT2);
1168 
1169   /**
1170    * synchronize code that uses this standard formatter for dates with minutes and seconds
1171    */
1172   final static SimpleDateFormat dateMinutesSecondsFormat = new SimpleDateFormat(
1173       DATE_MINUTES_SECONDS_FORMAT);
1174 
1175   /**
1176    * synchronize code that uses this standard formatter for dates with minutes and seconds
1177    */
1178   final static SimpleDateFormat dateMinutesSecondsNoSlashFormat = new SimpleDateFormat(
1179       DATE_MINUTES_SECONDS_NO_SLASH_FORMAT);
1180 
1181   /**
1182    * <pre> format: yyyy/MM/dd HH:mm:ss.SSS synchronize code that uses this standard formatter for timestamps </pre>
1183    */
1184   final static SimpleDateFormat timestampFormat = new SimpleDateFormat(TIMESTAMP_FORMAT);
1185 
1186   /**
1187    * synchronize code that uses this standard formatter for timestamps
1188    */
1189   final static SimpleDateFormat timestampNoSlashFormat = new SimpleDateFormat(
1190       TIMESTAMP_NO_SLASH_FORMAT);
1191 
1192   /**
1193    * If false, throw an assertException, and give a reason
1194    * 
1195    * @param isTrue
1196    * @param reason
1197    */
1198   public static void assertion(boolean isTrue, String reason) {
1199     if (!isTrue) {
1200       throw new RuntimeException(reason);
1201     }
1202 
1203   }
1204 
1205   /**
1206    * Field lastId.
1207    */
1208   private static char[] lastId = convertLongToStringSmall(new Date().getTime())
1209       .toCharArray();
1210 
1211   /**
1212    * null safe iterator getter if the type if collection
1213    * 
1214    * @param collection
1215    * @return the iterator
1216    */
1217   public static Iterator iterator(Object collection) {
1218     if (collection == null) {
1219       return null;
1220     }
1221     // array list doesnt need an iterator
1222     if (collection instanceof Collection
1223         && !(collection instanceof ArrayList)) {
1224       return ((Collection) collection).iterator();
1225     }
1226     return null;
1227   }
1228 
1229   /**
1230    * Null safe array length or map
1231    * 
1232    * @param arrayOrCollection
1233    * @return the length of the array (0 for null)
1234    */
1235   public static int length(Object arrayOrCollection) {
1236     if (arrayOrCollection == null) {
1237       return 0;
1238     }
1239     if (arrayOrCollection.getClass().isArray()) {
1240       return Array.getLength(arrayOrCollection);
1241     }
1242     if (arrayOrCollection instanceof Collection) {
1243       return ((Collection) arrayOrCollection).size();
1244     }
1245     if (arrayOrCollection instanceof Map) {
1246       return ((Map) arrayOrCollection).size();
1247     }
1248     // simple non array non collection object
1249     return 1;
1250   }
1251 
1252   /**
1253    * If array, get the element based on index, if Collection, get it based on
1254    * iterator.
1255    * 
1256    * @param arrayOrCollection
1257    * @param iterator
1258    * @param index
1259    * @return the object
1260    */
1261   public static Object next(Object arrayOrCollection, Iterator iterator,
1262       int index) {
1263     if (arrayOrCollection.getClass().isArray()) {
1264       return Array.get(arrayOrCollection, index);
1265     }
1266     if (arrayOrCollection instanceof ArrayList) {
1267       return ((ArrayList) arrayOrCollection).get(index);
1268     }
1269     if (arrayOrCollection instanceof Collection) {
1270       return iterator.next();
1271     }
1272     // simple object
1273     if (0 == index) {
1274       return arrayOrCollection;
1275     }
1276     throw new RuntimeException("Invalid class type: "
1277         + arrayOrCollection.getClass().getName());
1278   }
1279 
1280   /**
1281    * Remove the iterator or index
1282    * 
1283    * @param arrayOrCollection
1284    * @param index
1285    * @return the object list or new array
1286    */
1287   public static Object remove(Object arrayOrCollection,
1288       int index) {
1289     return remove(arrayOrCollection, null, index);
1290   }
1291 
1292   /**
1293    * Remove the iterator or index
1294    * 
1295    * @param arrayOrCollection
1296    * @param iterator
1297    * @param index
1298    * @return the object list or new array
1299    */
1300   public static Object remove(Object arrayOrCollection, Iterator iterator,
1301       int index) {
1302 
1303     //if theres an iterator, just use that
1304     if (iterator != null) {
1305       iterator.remove();
1306       return arrayOrCollection;
1307     }
1308     if (arrayOrCollection.getClass().isArray()) {
1309       int newLength = Array.getLength(arrayOrCollection) - 1;
1310       Object newArray = Array.newInstance(
1311           arrayOrCollection.getClass().getComponentType(), newLength);
1312       if (newLength == 0) {
1313         return newArray;
1314       }
1315       if (index > 0) {
1316         System.arraycopy(arrayOrCollection, 0, newArray, 0, index);
1317       }
1318       if (index < newLength) {
1319         System
1320             .arraycopy(arrayOrCollection, index + 1, newArray, index, newLength - index);
1321       }
1322       return newArray;
1323     }
1324     if (arrayOrCollection instanceof List) {
1325       ((List) arrayOrCollection).remove(index);
1326       return arrayOrCollection;
1327     } else if (arrayOrCollection instanceof Collection) {
1328       //this should work unless there are duplicates or something weird
1329       ((Collection) arrayOrCollection).remove(get(arrayOrCollection, index));
1330       return arrayOrCollection;
1331     }
1332     throw new RuntimeException("Invalid class type: "
1333         + arrayOrCollection.getClass().getName());
1334   }
1335 
1336   /**
1337    * print the simple names of a list of classes
1338    * @param object
1339    * @return the simple names
1340    */
1341   public static String classesString(Object object) {
1342     StringBuilder result = new StringBuilder();
1343     if (object.getClass().isArray()) {
1344       int length = Array.getLength(object);
1345       for (int i = 0; i < length; i++) {
1346         result.append(((Class) Array.get(object, i)).getSimpleName());
1347         if (i < length - 1) {
1348           result.append(", ");
1349         }
1350       }
1351       return result.toString();
1352     }
1353 
1354     throw new RuntimeException("Not implemented: " + className(object));
1355   }
1356 
1357   /**
1358    * null safe classname method, max out at 20
1359    * 
1360    * @param object
1361    * @return the classname
1362    */
1363   public static String classNameCollection(Object object) {
1364     if (object == null) {
1365       return null;
1366     }
1367     StringBuffer result = new StringBuffer();
1368 
1369     Iterator iterator = iterator(object);
1370     int length = length(object);
1371     for (int i = 0; i < length && i < 20; i++) {
1372       result.append(className(next(object, iterator, i)));
1373       if (i != length - 1) {
1374         result.append(", ");
1375       }
1376     }
1377     return result.toString();
1378   }
1379 
1380   /**
1381    * null safe classname method, gets the unenhanced name
1382    * 
1383    * @param object
1384    * @return the classname
1385    */
1386   public static String className(Object object) {
1387     return object == null ? null : object.getClass().getName();
1388   }
1389 
1390   /**
1391    * Convert a list to an array with the type of the first element e.g. if it
1392    * is a list of Person objects, then the array is Person[]
1393    * 
1394    * @param objectOrArrayOrCollection
1395    *            is a list
1396    * @return the array of objects with type of the first element in the list
1397    */
1398   public static Object toArray(Object objectOrArrayOrCollection) {
1399     // do this before length since if array with null in it, we want ti get
1400     // it back
1401     if (objectOrArrayOrCollection != null
1402         && objectOrArrayOrCollection.getClass().isArray()) {
1403       return objectOrArrayOrCollection;
1404     }
1405     int length = length(objectOrArrayOrCollection);
1406     if (length == 0) {
1407       return null;
1408     }
1409 
1410     if (objectOrArrayOrCollection instanceof Collection) {
1411       Collection collection = (Collection) objectOrArrayOrCollection;
1412       Object first = collection.iterator().next();
1413       return toArray(collection, first == null ? Object.class : first
1414           .getClass());
1415     }
1416     // make an array of the type of object passed in, size one
1417     Object array = Array.newInstance(objectOrArrayOrCollection.getClass(),
1418         1);
1419     Array.set(array, 0, objectOrArrayOrCollection);
1420     return array;
1421   }
1422 
1423   /**
1424    * convert a list into an array of type of theClass
1425    * @param <T> is the type of the array
1426    * @param collection list to convert
1427    * @param theClass type of array to return
1428    * @return array of type theClass[] filled with the objects from list
1429    */
1430   @SuppressWarnings("unchecked")
1431   public static <T> T[] toArray(Collection collection, Class<T> theClass) {
1432     if (collection == null || collection.size() == 0) {
1433       return null;
1434     }
1435 
1436     return (T[]) collection.toArray((Object[]) Array.newInstance(theClass,
1437         collection.size()));
1438 
1439   }
1440 
1441   /**
1442    * replace a string or strings from a string, and put the output in a string
1443    * buffer. This does not recurse
1444    * 
1445    * @param text
1446    *            string to look in
1447    * @param searchFor
1448    *            string array to search for
1449    * @param replaceWith
1450    *            string array to replace with
1451    * @return the string
1452    */
1453   public static String replace(String text, Object searchFor,
1454       Object replaceWith) {
1455     return replace(null, null, text, searchFor, replaceWith, false, 0,
1456         false);
1457   }
1458 
1459   /**
1460    * replace a string or strings from a string, and put the output in a string
1461    * buffer
1462    * 
1463    * @param text
1464    *            string to look in
1465    * @param searchFor
1466    *            string array to search for
1467    * @param replaceWith
1468    *            string array to replace with
1469    * @param recurse
1470    *            if true then do multiple replaces (on the replacements)
1471    * @return the string
1472    */
1473   public static String replace(String text, Object searchFor,
1474       Object replaceWith, boolean recurse) {
1475     return replace(null, null, text, searchFor, replaceWith, recurse,
1476         recurse ? length(searchFor) : 0, false);
1477   }
1478 
1479   /**
1480    * replace a string or strings from a string, and put the output in a string
1481    * buffer
1482    * 
1483    * @param text
1484    *            string to look in
1485    * @param searchFor
1486    *            string array to search for
1487    * @param replaceWith
1488    *            string array to replace with
1489    * @param recurse
1490    *            if true then do multiple replaces (on the replacements)
1491    * @param removeIfFound
1492    *            true if removing from searchFor and replaceWith if found
1493    * @return the string
1494    */
1495   public static String replace(String text, Object searchFor,
1496       Object replaceWith, boolean recurse, boolean removeIfFound) {
1497     return replace(null, null, text, searchFor, replaceWith, recurse,
1498         recurse ? length(searchFor) : 0, removeIfFound);
1499   }
1500 
1501   /**
1502    * <p>
1503    * Replaces all occurrences of a String within another String.
1504    * </p>
1505    * 
1506    * <p>
1507    * A <code>null</code> reference passed to this method is a no-op.
1508    * </p>
1509    * 
1510    * <pre>
1511    * replace(null, *, *)        = null
1512    * replace(&quot;&quot;, *, *)          = &quot;&quot;
1513    * replace(&quot;any&quot;, null, *)    = &quot;any&quot;
1514    * replace(&quot;any&quot;, *, null)    = &quot;any&quot;
1515    * replace(&quot;any&quot;, &quot;&quot;, *)      = &quot;any&quot;
1516    * replace(&quot;aba&quot;, &quot;a&quot;, null)  = &quot;aba&quot;
1517    * replace(&quot;aba&quot;, &quot;a&quot;, &quot;&quot;)    = &quot;b&quot;
1518    * replace(&quot;aba&quot;, &quot;a&quot;, &quot;z&quot;)   = &quot;zbz&quot;
1519    * </pre>
1520    * 
1521    * @see #replace(String text, String repl, String with, int max)
1522    * @param text
1523    *            text to search and replace in, may be null
1524    * @param repl
1525    *            the String to search for, may be null
1526    * @param with
1527    *            the String to replace with, may be null
1528    * @return the text with any replacements processed, <code>null</code> if
1529    *         null String input
1530    */
1531   public static String replace(String text, String repl, String with) {
1532     return replace(text, repl, with, -1);
1533   }
1534 
1535   /**
1536    * <p>
1537    * Replaces a String with another String inside a larger String, for the
1538    * first <code>max</code> values of the search String.
1539    * </p>
1540    * 
1541    * <p>
1542    * A <code>null</code> reference passed to this method is a no-op.
1543    * </p>
1544    * 
1545    * <pre>
1546    * replace(null, *, *, *)         = null
1547    * replace(&quot;&quot;, *, *, *)           = &quot;&quot;
1548    * replace(&quot;any&quot;, null, *, *)     = &quot;any&quot;
1549    * replace(&quot;any&quot;, *, null, *)     = &quot;any&quot;
1550    * replace(&quot;any&quot;, &quot;&quot;, *, *)       = &quot;any&quot;
1551    * replace(&quot;any&quot;, *, *, 0)        = &quot;any&quot;
1552    * replace(&quot;abaa&quot;, &quot;a&quot;, null, -1) = &quot;abaa&quot;
1553    * replace(&quot;abaa&quot;, &quot;a&quot;, &quot;&quot;, -1)   = &quot;b&quot;
1554    * replace(&quot;abaa&quot;, &quot;a&quot;, &quot;z&quot;, 0)   = &quot;abaa&quot;
1555    * replace(&quot;abaa&quot;, &quot;a&quot;, &quot;z&quot;, 1)   = &quot;zbaa&quot;
1556    * replace(&quot;abaa&quot;, &quot;a&quot;, &quot;z&quot;, 2)   = &quot;zbza&quot;
1557    * replace(&quot;abaa&quot;, &quot;a&quot;, &quot;z&quot;, -1)  = &quot;zbzz&quot;
1558    * </pre>
1559    * 
1560    * @param text
1561    *            text to search and replace in, may be null
1562    * @param repl
1563    *            the String to search for, may be null
1564    * @param with
1565    *            the String to replace with, may be null
1566    * @param max
1567    *            maximum number of values to replace, or <code>-1</code> if
1568    *            no maximum
1569    * @return the text with any replacements processed, <code>null</code> if
1570    *         null String input
1571    */
1572   public static String replace(String text, String repl, String with, int max) {
1573     if (text == null || isEmpty(repl) || with == null || max == 0) {
1574       return text;
1575     }
1576 
1577     StringBuffer buf = new StringBuffer(text.length());
1578     int start = 0, end = 0;
1579     while ((end = text.indexOf(repl, start)) != -1) {
1580       buf.append(text.substring(start, end)).append(with);
1581       start = end + repl.length();
1582 
1583       if (--max == 0) {
1584         break;
1585       }
1586     }
1587     buf.append(text.substring(start));
1588     return buf.toString();
1589   }
1590 
1591   /**
1592    * <p>
1593    * Checks if a String is empty ("") or null.
1594    * </p>
1595    * 
1596    * <pre>
1597    * isEmpty(null)      = true
1598    * isEmpty(&quot;&quot;)        = true
1599    * isEmpty(&quot; &quot;)       = false
1600    * isEmpty(&quot;bob&quot;)     = false
1601    * isEmpty(&quot;  bob  &quot;) = false
1602    * </pre>
1603    * 
1604    * <p>
1605    * NOTE: This method changed in Lang version 2.0. It no longer trims the
1606    * String. That functionality is available in isBlank().
1607    * </p>
1608    * 
1609    * @param str
1610    *            the String to check, may be null
1611    * @return <code>true</code> if the String is empty or null
1612    */
1613   public static boolean isEmpty(String str) {
1614     return str == null || str.length() == 0;
1615   }
1616 
1617   /**
1618    * replace a string or strings from a string, and put the output in a string
1619    * buffer. This does not recurse
1620    * 
1621    * @param outBuffer
1622    *            stringbuffer to write to
1623    * @param text
1624    *            string to look in
1625    * @param searchFor
1626    *            string array to search for
1627    * @param replaceWith
1628    *            string array to replace with
1629    */
1630   public static void replace(StringBuffer outBuffer, String text,
1631       Object searchFor, Object replaceWith) {
1632     replace(outBuffer, null, text, searchFor, replaceWith, false, 0, false);
1633   }
1634 
1635   /**
1636    * replace a string or strings from a string, and put the output in a string
1637    * buffer
1638    * 
1639    * @param outBuffer
1640    *            stringbuffer to write to
1641    * @param text
1642    *            string to look in
1643    * @param searchFor
1644    *            string array to search for
1645    * @param replaceWith
1646    *            string array to replace with
1647    * @param recurse
1648    *            if true then do multiple replaces (on the replacements)
1649    */
1650   public static void replace(StringBuffer outBuffer, String text,
1651       Object searchFor, Object replaceWith, boolean recurse) {
1652     replace(outBuffer, null, text, searchFor, replaceWith, recurse,
1653         recurse ? length(searchFor) : 0, false);
1654   }
1655 
1656   /**
1657    * replace a string with other strings, and either write to outWriter, or
1658    * StringBuffer, and if StringBuffer potentially return a string. If
1659    * outBuffer and outWriter are null, then return the String
1660    * 
1661    * @param outBuffer
1662    *            stringbuffer to write to, or null to not
1663    * @param outWriter
1664    *            Writer to write to, or null to not.
1665    * @param text
1666    *            string to look in
1667    * @param searchFor
1668    *            string array to search for, or string, or list
1669    * @param replaceWith
1670    *            string array to replace with, or string, or list
1671    * @param recurse
1672    *            if true then do multiple replaces (on the replacements)
1673    * @param timeToLive
1674    *            if recursing, prevent endless loops
1675    * @param removeIfFound
1676    *            true if removing from searchFor and replaceWith if found
1677    * @return the String if outBuffer and outWriter are null
1678    * @throws IndexOutOfBoundsException
1679    *             if the lengths of the arrays are not the same (null is ok,
1680    *             and/or size 0)
1681    * @throws IllegalArgumentException
1682    *             if the search is recursive and there is an endless loop due
1683    *             to outputs of one being inputs to another
1684    */
1685   private static String replace(StringBuffer outBuffer, Writer outWriter,
1686       String text, Object searchFor, Object replaceWith, boolean recurse,
1687       int timeToLive, boolean removeIfFound) {
1688 
1689     // if recursing, we need to get the string, then print to buffer (since
1690     // we need multiple passes)
1691     if (!recurse) {
1692       return replaceHelper(outBuffer, outWriter, text, searchFor,
1693           replaceWith, recurse, timeToLive, removeIfFound);
1694     }
1695     // get the string
1696     String result = replaceHelper(null, null, text, searchFor, replaceWith,
1697         recurse, timeToLive, removeIfFound);
1698     if (outBuffer != null) {
1699       outBuffer.append(result);
1700       return null;
1701     }
1702 
1703     if (outWriter != null) {
1704       try {
1705         outWriter.write(result);
1706       } catch (IOException ioe) {
1707         throw new RuntimeException(ioe);
1708       }
1709       return null;
1710     }
1711 
1712     return result;
1713 
1714   }
1715 
1716   /**
1717    * replace a string or strings from a string, and put the output in a string
1718    * buffer. This does not recurse
1719    * 
1720    * @param outWriter
1721    *            writer to write to
1722    * @param text
1723    *            string to look in
1724    * @param searchFor
1725    *            string array to search for
1726    * @param replaceWith
1727    *            string array to replace with
1728    */
1729   public static void replace(Writer outWriter, String text, Object searchFor,
1730       Object replaceWith) {
1731     replace(null, outWriter, text, searchFor, replaceWith, false, 0, false);
1732   }
1733 
1734   /**
1735    * replace a string or strings from a string, and put the output in a string
1736    * buffer
1737    * 
1738    * @param outWriter
1739    *            writer to write to
1740    * @param text
1741    *            string to look in
1742    * @param searchFor
1743    *            string array to search for
1744    * @param replaceWith
1745    *            string array to replace with
1746    * @param recurse
1747    *            if true then do multiple replaces (on the replacements)
1748    */
1749   public static void replace(Writer outWriter, String text, Object searchFor,
1750       Object replaceWith, boolean recurse) {
1751     replace(null, outWriter, text, searchFor, replaceWith, recurse,
1752         recurse ? length(searchFor) : 0, false);
1753   }
1754 
1755   /**
1756    * replace a string with other strings, and either write to outWriter, or
1757    * StringBuffer, and if StringBuffer potentially return a string. If
1758    * outBuffer and outWriter are null, then return the String
1759    * 
1760    * @param outBuffer
1761    *            stringbuffer to write to, or null to not
1762    * @param outWriter
1763    *            Writer to write to, or null to not.
1764    * @param text
1765    *            string to look in
1766    * @param searchFor
1767    *            string array to search for, or string, or list
1768    * @param replaceWith
1769    *            string array to replace with, or string, or list
1770    * @param recurse
1771    *            if true then do multiple replaces (on the replacements)
1772    * @param timeToLive
1773    *            if recursing, prevent endless loops
1774    * @param removeIfFound
1775    *            true if removing from searchFor and replaceWith if found
1776    * @return the String if outBuffer and outWriter are null
1777    * @throws IllegalArgumentException
1778    *             if the search is recursive and there is an endless loop due
1779    *             to outputs of one being inputs to another
1780    * @throws IndexOutOfBoundsException
1781    *             if the lengths of the arrays are not the same (null is ok,
1782    *             and/or size 0)
1783    */
1784   private static String replaceHelper(StringBuffer outBuffer,
1785       Writer outWriter, String text, Object searchFor,
1786       Object replaceWith, boolean recurse, int timeToLive,
1787       boolean removeIfFound) {
1788 
1789     try {
1790       // if recursing, this shouldnt be less than 0
1791       if (timeToLive < 0) {
1792         throw new IllegalArgumentException("TimeToLive under 0: "
1793             + timeToLive + ", " + text);
1794       }
1795 
1796       int searchForLength = length(searchFor);
1797       boolean done = false;
1798       // no need to do anything
1799       if (isEmpty(text)) {
1800         return text;
1801       }
1802       // need to write the input to output, later
1803       if (searchForLength == 0) {
1804         done = true;
1805       }
1806 
1807       boolean[] noMoreMatchesForReplIndex = null;
1808       int inputIndex = -1;
1809       int replaceIndex = -1;
1810       long resultPacked = -1;
1811 
1812       if (!done) {
1813         // make sure lengths are ok, these need to be equal
1814         if (searchForLength != length(replaceWith)) {
1815           throw new IndexOutOfBoundsException("Lengths dont match: "
1816               + searchForLength + ", " + length(replaceWith));
1817         }
1818 
1819         // keep track of which still have matches
1820         noMoreMatchesForReplIndex = new boolean[searchForLength];
1821 
1822         // index of replace array that will replace the search string
1823         // found
1824 
1825         resultPacked = findNextIndexHelper(searchForLength, searchFor,
1826             replaceWith,
1827             noMoreMatchesForReplIndex, text, 0);
1828 
1829         inputIndex = unpackInt(resultPacked, true);
1830         replaceIndex = unpackInt(resultPacked, false);
1831       }
1832 
1833       // get a good guess on the size of the result buffer so it doesnt
1834       // have to double if it
1835       // goes over a bit
1836       boolean writeToWriter = outWriter != null;
1837 
1838       // no search strings found, we are done
1839       if (done || inputIndex == -1) {
1840         if (writeToWriter) {
1841           outWriter.write(text, 0, text.length());
1842           return null;
1843         }
1844         if (outBuffer != null) {
1845           appendSubstring(outBuffer, text, 0, text.length());
1846           return null;
1847         }
1848         return text;
1849       }
1850 
1851       // no buffer if writing to writer
1852       StringBuffer bufferToWriteTo = outBuffer != null ? outBuffer
1853           : (writeToWriter ? null : new StringBuffer(text.length()
1854               + replaceStringsBufferIncrease(text, searchFor,
1855                   replaceWith)));
1856 
1857       String searchString = null;
1858       String replaceString = null;
1859 
1860       int start = 0;
1861 
1862       while (inputIndex != -1) {
1863 
1864         searchString = (String) get(searchFor, replaceIndex);
1865         replaceString = (String) get(replaceWith, replaceIndex);
1866         if (writeToWriter) {
1867           outWriter.write(text, start, inputIndex - start);
1868           outWriter.write(replaceString);
1869         } else {
1870           appendSubstring(bufferToWriteTo, text, start, inputIndex)
1871               .append(replaceString);
1872         }
1873 
1874         if (removeIfFound) {
1875           // better be an iterator based find replace
1876           searchFor = remove(searchFor, replaceIndex);
1877           replaceWith = remove(replaceWith, replaceIndex);
1878           noMoreMatchesForReplIndex = (boolean[]) remove(noMoreMatchesForReplIndex,
1879               replaceIndex);
1880           // we now have a lesser size if we removed one
1881           searchForLength--;
1882         }
1883 
1884         start = inputIndex + searchString.length();
1885 
1886         resultPacked = findNextIndexHelper(searchForLength, searchFor,
1887             replaceWith,
1888             noMoreMatchesForReplIndex, text, start);
1889         inputIndex = unpackInt(resultPacked, true);
1890         replaceIndex = unpackInt(resultPacked, false);
1891       }
1892       if (writeToWriter) {
1893         outWriter.write(text, start, text.length() - start);
1894 
1895       } else {
1896         appendSubstring(bufferToWriteTo, text, start, text.length());
1897       }
1898 
1899       // no need to convert to string if incoming buffer or writer
1900       if (writeToWriter || outBuffer != null) {
1901         if (recurse) {
1902           throw new IllegalArgumentException(
1903               "Cannot recurse and write to existing buffer or writer!");
1904         }
1905         return null;
1906       }
1907       String resultString = bufferToWriteTo.toString();
1908 
1909       if (recurse) {
1910         return replaceHelper(outBuffer, outWriter, resultString,
1911             searchFor, replaceWith, recurse, timeToLive - 1, false);
1912       }
1913       // this might be null for writer
1914       return resultString;
1915     } catch (IOException ioe) {
1916       throw new RuntimeException(ioe);
1917     }
1918   }
1919 
1920   /**
1921    * give a best guess on buffer increase for String[] replace get a good
1922    * guess on the size of the result buffer so it doesnt have to double if it
1923    * goes over a bit
1924    * 
1925    * @param text
1926    * @param repl
1927    * @param with
1928    * @return the increase, with 20% cap
1929    */
1930   static int replaceStringsBufferIncrease(String text, Object repl,
1931       Object with) {
1932     // count the greaters
1933     int increase = 0;
1934     Iterator iteratorReplace = iterator(repl);
1935     Iterator iteratorWith = iterator(with);
1936     int replLength = length(repl);
1937     String currentRepl = null;
1938     String currentWith = null;
1939     for (int i = 0; i < replLength; i++) {
1940       currentRepl = (String) next(repl, iteratorReplace, i);
1941       currentWith = (String) next(with, iteratorWith, i);
1942       if (currentRepl == null || currentWith == null) {
1943         throw new NullPointerException("Replace string is null: "
1944             + text + ", " + currentRepl + ", " + currentWith);
1945       }
1946       int greater = currentWith.length() - currentRepl.length();
1947       increase += greater > 0 ? 3 * greater : 0; // assume 3 matches
1948     }
1949     // have upper-bound at 20% increase, then let Java take over
1950     increase = Math.min(increase, text.length() / 5);
1951     return increase;
1952   }
1953 
1954   /**
1955    * Helper method to find the next match in an array of strings replace
1956    * 
1957    * @param searchForLength
1958    * @param searchFor
1959    * @param replaceWith
1960    * @param noMoreMatchesForReplIndex
1961    * @param input
1962    * @param start
1963    *            is where to start looking
1964    * @return result packed into a long, inputIndex first, then replaceIndex
1965    */
1966   private static long findNextIndexHelper(int searchForLength,
1967       Object searchFor, Object replaceWith, boolean[] noMoreMatchesForReplIndex,
1968       String input, int start) {
1969 
1970     int inputIndex = -1;
1971     int replaceIndex = -1;
1972 
1973     Iterator iteratorSearchFor = iterator(searchFor);
1974     Iterator iteratorReplaceWith = iterator(replaceWith);
1975 
1976     String currentSearchFor = null;
1977     String currentReplaceWith = null;
1978     int tempIndex = -1;
1979     for (int i = 0; i < searchForLength; i++) {
1980       currentSearchFor = (String) next(searchFor, iteratorSearchFor, i);
1981       currentReplaceWith = (String) next(replaceWith,
1982           iteratorReplaceWith, i);
1983       if (noMoreMatchesForReplIndex[i] || isEmpty(currentSearchFor)
1984           || currentReplaceWith == null) {
1985         continue;
1986       }
1987       tempIndex = input.indexOf(currentSearchFor, start);
1988 
1989       // see if we need to keep searching for this
1990       noMoreMatchesForReplIndex[i] = tempIndex == -1;
1991 
1992       if (tempIndex != -1 && (inputIndex == -1 || tempIndex < inputIndex)) {
1993         inputIndex = tempIndex;
1994         replaceIndex = i;
1995       }
1996 
1997     }
1998     // dont create an array, no more objects
1999     long resultPacked = packInts(inputIndex, replaceIndex);
2000     return resultPacked;
2001   }
2002 
2003   /**
2004    * pack two ints into a long. Note: the first is held in the left bits, the
2005    * second is held in the right bits
2006    * 
2007    * @param first
2008    *            is first int
2009    * @param second
2010    *            is second int
2011    * @return the long which has two ints in there
2012    */
2013   public static long packInts(int first, int second) {
2014     long result = first;
2015     result <<= 32;
2016     result |= second;
2017     return result;
2018   }
2019 
2020   /**
2021    * take a long
2022    * 
2023    * @param theLong
2024    *            to unpack
2025    * @param isFirst
2026    *            true for first, false for second
2027    * @return one of the packed ints, first or second
2028    */
2029   public static int unpackInt(long theLong, boolean isFirst) {
2030 
2031     int result = 0;
2032     // put this in the position of the second one
2033     if (isFirst) {
2034       theLong >>= 32;
2035     }
2036     // only look at right part
2037     result = (int) (theLong & 0xffffffff);
2038     return result;
2039   }
2040 
2041   /**
2042    * append a substring to a stringbuffer. removes dependency on substring
2043    * which creates objects
2044    * 
2045    * @param buf
2046    *            stringbuffer
2047    * @param string
2048    *            source string
2049    * @param start
2050    *            start index of source string
2051    * @param end
2052    *            end index of source string
2053    * @return the string buffer for chaining
2054    */
2055   private static StringBuffer appendSubstring(StringBuffer buf,
2056       String string, int start, int end) {
2057     for (int i = start; i < end; i++) {
2058       buf.append(string.charAt(i));
2059     }
2060     return buf;
2061   }
2062 
2063   /**
2064    * fail safe toString for Exception blocks, and include the stack
2065    * if there is a problem with toString()
2066    * @param object
2067    * @return the toStringSafe string
2068    */
2069   @SuppressWarnings("unchecked")
2070   public static String toStringSafe(Object object) {
2071     if (object == null) {
2072       return null;
2073     }
2074 
2075     try {
2076       //give size and type if collection
2077       if (object instanceof Collection) {
2078         Collection<Object> collection = (Collection<Object>) object;
2079         int collectionSize = collection.size();
2080         if (collectionSize == 0) {
2081           return "Empty " + object.getClass().getSimpleName();
2082         }
2083         Object first = collection.iterator().next();
2084         return object.getClass().getSimpleName() + " of size "
2085             + collectionSize + " with first type: " +
2086             (first == null ? null : first.getClass());
2087       }
2088 
2089       return object.toString();
2090     } catch (Exception e) {
2091       return "<<exception>> " + object.getClass() + ":\n" + getFullStackTrace(e) + "\n";
2092     }
2093   }
2094 
2095   /**
2096    * get the boolean value for an object, cant be null or blank
2097    * 
2098    * @param object
2099    * @return the boolean
2100    */
2101   public static boolean booleanValue(Object object) {
2102     // first handle blanks
2103     if (nullOrBlank(object)) {
2104       throw new RuntimeException(
2105           "Expecting something which can be converted to boolean, but is null or blank: '"
2106               + object + "'");
2107     }
2108     // its not blank, just convert
2109     if (object instanceof Boolean) {
2110       return (Boolean) object;
2111     }
2112     if (object instanceof String) {
2113       String string = (String) object;
2114       if (equalsIgnoreCase(string, "true")
2115           || equalsIgnoreCase(string, "t")
2116           || equalsIgnoreCase(string, "yes")
2117           || equalsIgnoreCase(string, "y")) {
2118         return true;
2119       }
2120       if (equalsIgnoreCase(string, "false")
2121           || equalsIgnoreCase(string, "f")
2122           || equalsIgnoreCase(string, "no")
2123           || equalsIgnoreCase(string, "n")) {
2124         return false;
2125       }
2126       throw new RuntimeException(
2127           "Invalid string to boolean conversion: '" + string
2128               + "' expecting true|false or t|f or yes|no or y|n case insensitive");
2129 
2130     }
2131     throw new RuntimeException("Cant convert object to boolean: "
2132         + object.getClass());
2133 
2134   }
2135 
2136   /**
2137    * get the boolean value for an object
2138    * 
2139    * @param object
2140    * @param defaultBoolean
2141    *            if object is null or empty
2142    * @return the boolean
2143    */
2144   public static boolean booleanValue(Object object, boolean defaultBoolean) {
2145     if (nullOrBlank(object)) {
2146       return defaultBoolean;
2147     }
2148     return booleanValue(object);
2149   }
2150 
2151   /**
2152    * get the Boolean value for an object
2153    * 
2154    * @param object
2155    * @return the Boolean or null if null or empty
2156    */
2157   public static Boolean booleanObjectValue(Object object) {
2158     if (nullOrBlank(object)) {
2159       return null;
2160     }
2161     return booleanValue(object);
2162   }
2163 
2164   /**
2165    * is an object null or blank
2166    * 
2167    * @param object
2168    * @return true if null or blank
2169    */
2170   public static boolean nullOrBlank(Object object) {
2171     // first handle blanks and nulls
2172     if (object == null) {
2173       return true;
2174     }
2175     if (object instanceof String && isBlank(((String) object))) {
2176       return true;
2177     }
2178     return false;
2179 
2180   }
2181 
2182   /**
2183    * convert a string date into a long date (e.g. for xml export)
2184    * @param date
2185    * @return the long or null if the date was null or blank
2186    */
2187   public static Long dateLongValue(String date) {
2188     if (isBlank(date)) {
2189       return null;
2190     }
2191     Date dateObject = dateValue(date);
2192     return dateObject.getTime();
2193   }
2194 
2195   /**
2196    * web service format string
2197    */
2198   private static final String TIMESTAMP_XML_FORMAT = "yyyy/MM/dd HH:mm:ss.SSS";
2199 
2200   /**
2201    * date object to a string: 
2202    * @param date
2203    * @return the long or null if the date was null or blank
2204    */
2205   public static String dateStringValue(Date date) {
2206     if (date == null) {
2207       return null;
2208     }
2209     SimpleDateFormat simpleDateFormat = new SimpleDateFormat(TIMESTAMP_XML_FORMAT);
2210     return simpleDateFormat.format(date);
2211   }
2212 
2213   /**
2214    * date object to a string: 
2215    * @param theDate
2216    * @return the long or null if the date was null or blank
2217    */
2218   public static String dateStringValue(Long theDate) {
2219     if (theDate == null) {
2220       return null;
2221     }
2222     return dateStringValue(new Date(theDate));
2223   }
2224 
2225   /**
2226    * <pre>
2227    * Convert an object to a java.util.Date.  allows, dates, null, blank, 
2228    * yyyymmdd or yyyymmdd hh24:mm:ss
2229    * or yyyy/MM/dd HH:mm:ss.SSS
2230    * </pre>
2231    * @param inputObject
2232    *          is the String or Date to convert
2233    * 
2234    * @return the Date
2235    */
2236   public static Date dateValue(Object inputObject) {
2237     if (inputObject == null) {
2238       return null;
2239     }
2240 
2241     if (inputObject instanceof java.util.Date) {
2242       return (Date) inputObject;
2243     }
2244 
2245     if (inputObject instanceof String) {
2246       String input = (String) inputObject;
2247       //trim and handle null and empty
2248       if (isBlank(input)) {
2249         return null;
2250       }
2251 
2252       try {
2253         if (input.length() == 8) {
2254 
2255           return dateFormat().parse(input);
2256         }
2257         if (!contains(input, '.')) {
2258           if (contains(input, '/')) {
2259             return dateMinutesSecondsFormat.parse(input);
2260           }
2261           //else no slash
2262           return dateMinutesSecondsNoSlashFormat.parse(input);
2263         }
2264         if (contains(input, '/')) {
2265           //see if the period is 6 back
2266           int lastDotIndex = input.lastIndexOf('.');
2267           if (lastDotIndex == input.length() - 7) {
2268             String nonNanoInput = input.substring(0, input.length() - 3);
2269             Date date = timestampFormat.parse(nonNanoInput);
2270             //get the last 3
2271             String lastThree = input.substring(input.length() - 3, input.length());
2272             int lastThreeInt = Integer.parseInt(lastThree);
2273             Timestamp timestamp = new Timestamp(date.getTime());
2274             timestamp.setNanos(timestamp.getNanos() + (lastThreeInt * 1000));
2275             return timestamp;
2276           }
2277           return timestampFormat.parse(input);
2278         }
2279         //else no slash
2280         return timestampNoSlashFormat.parse(input);
2281       } catch (ParseException pe) {
2282         throw new RuntimeException(errorStart + toStringForLog(input));
2283       }
2284     }
2285 
2286     throw new RuntimeException("Cannot convert Object to date : "
2287         + toStringForLog(inputObject));
2288   }
2289 
2290   /**
2291    * match regex pattern yyyy-mm-dd or yyyy/mm/dd
2292    */
2293   private static Pattern datePattern_yyyy_mm_dd = Pattern
2294       .compile("^(\\d{4})[^\\d]+(\\d{1,2})[^\\d]+(\\d{1,2})$");
2295 
2296   /**
2297    * match regex pattern dd-mon-yyyy or dd/mon/yyyy
2298    */
2299   private static Pattern datePattern_dd_mon_yyyy = Pattern
2300       .compile("^(\\d{1,2})[^\\d]+([a-zA-Z]{3,15})[^\\d]+(\\d{4})$");
2301 
2302   /**
2303    * match regex pattern yyyy-mm-dd hh:mm:ss or yyyy/mm/dd hh:mm:ss
2304    */
2305   private static Pattern datePattern_yyyy_mm_dd_hhmmss = Pattern
2306       .compile("^(\\d{4})[^\\d]+(\\d{1,2})[^\\d]+(\\d{1,2})[^\\d]+(\\d{1,2})[^\\d]+(\\d{1,2})[^\\d]+(\\d{1,2})$");
2307 
2308   /**
2309    * match regex pattern dd-mon-yyyy hh:mm:ss or dd/mon/yyyy hh:mm:ss
2310    */
2311   private static Pattern datePattern_dd_mon_yyyy_hhmmss = Pattern
2312       .compile("^(\\d{1,2})[^\\d]+([a-zA-Z]{3,15})[^\\d]+(\\d{4})[^\\d]+(\\d{1,2})[^\\d]+(\\d{1,2})[^\\d]+(\\d{1,2})$");
2313 
2314   /**
2315    * match regex pattern yyyy-mm-dd hh:mm:ss.SSS or yyyy/mm/dd hh:mm:ss.SSS
2316    */
2317   private static Pattern datePattern_yyyy_mm_dd_hhmmss_SSS = Pattern
2318       .compile("^(\\d{4})[^\\d]+(\\d{1,2})[^\\d]+(\\d{1,2})[^\\d]+(\\d{1,2})[^\\d]+(\\d{1,2})[^\\d]+(\\d{1,2})[^\\d]+(\\d{1,3})$");
2319 
2320   /**
2321    * match regex pattern dd-mon-yyyy hh:mm:ss.SSS or dd/mon/yyyy hh:mm:ss.SSS
2322    */
2323   private static Pattern datePattern_dd_mon_yyyy_hhmmss_SSS = Pattern
2324       .compile("^(\\d{1,2})[^\\d]+([a-zA-Z]{3,15})[^\\d]+(\\d{4})[^\\d]+(\\d{1,2})[^\\d]+(\\d{1,2})[^\\d]+(\\d{1,2})[^\\d]+(\\d{1,3})$");
2325 
2326   /**
2327    * take as input:
2328    * yyyy/mm/dd
2329    * yyyy-mm-dd
2330    * dd-mon-yyyy
2331    * yyyy/mm/dd hh:mm:ss
2332    * dd-mon-yyyy hh:mm:ss
2333    * yyyy/mm/dd hh:mm:ss.SSS
2334    * dd-mon-yyyy hh:mm:ss.SSS
2335    * @param input
2336    * @return the date
2337    */
2338   public static Date stringToDate2(String input) {
2339 
2340     if (isBlank(input)) {
2341       return null;
2342     }
2343     input = input.trim();
2344     Matcher matcher = null;
2345 
2346     int month = 0;
2347     int day = 0;
2348     int year = 0;
2349     int hour = 0;
2350     int minute = 0;
2351     int second = 0;
2352     int milli = 0;
2353 
2354     boolean foundMatch = false;
2355 
2356     //yyyy/mm/dd
2357     if (!foundMatch) {
2358       matcher = datePattern_yyyy_mm_dd.matcher(input);
2359       if (matcher.matches()) {
2360         year = intValue(matcher.group(1));
2361         month = intValue(matcher.group(2));
2362         day = intValue(matcher.group(3));
2363         foundMatch = true;
2364       }
2365     }
2366 
2367     //dd-mon-yyyy
2368     if (!foundMatch) {
2369       matcher = datePattern_dd_mon_yyyy.matcher(input);
2370       if (matcher.matches()) {
2371         day = intValue(matcher.group(1));
2372         month = monthInt(matcher.group(2));
2373         year = intValue(matcher.group(3));
2374         foundMatch = true;
2375       }
2376     }
2377 
2378     //yyyy/mm/dd hh:mm:ss
2379     if (!foundMatch) {
2380       matcher = datePattern_yyyy_mm_dd_hhmmss.matcher(input);
2381       if (matcher.matches()) {
2382         year = intValue(matcher.group(1));
2383         month = intValue(matcher.group(2));
2384         day = intValue(matcher.group(3));
2385         hour = intValue(matcher.group(4));
2386         minute = intValue(matcher.group(5));
2387         second = intValue(matcher.group(6));
2388         foundMatch = true;
2389       }
2390     }
2391 
2392     //dd-mon-yyyy hh:mm:ss
2393     if (!foundMatch) {
2394       matcher = datePattern_dd_mon_yyyy_hhmmss.matcher(input);
2395       if (matcher.matches()) {
2396         day = intValue(matcher.group(1));
2397         month = monthInt(matcher.group(2));
2398         year = intValue(matcher.group(3));
2399         hour = intValue(matcher.group(4));
2400         minute = intValue(matcher.group(5));
2401         second = intValue(matcher.group(6));
2402         foundMatch = true;
2403       }
2404     }
2405 
2406     //yyyy/mm/dd hh:mm:ss.SSS
2407     if (!foundMatch) {
2408       matcher = datePattern_yyyy_mm_dd_hhmmss_SSS.matcher(input);
2409       if (matcher.matches()) {
2410         year = intValue(matcher.group(1));
2411         month = intValue(matcher.group(2));
2412         day = intValue(matcher.group(3));
2413         hour = intValue(matcher.group(4));
2414         minute = intValue(matcher.group(5));
2415         second = intValue(matcher.group(6));
2416         milli = intValue(matcher.group(7));
2417         foundMatch = true;
2418       }
2419     }
2420 
2421     //dd-mon-yyyy hh:mm:ss.SSS
2422     if (!foundMatch) {
2423       matcher = datePattern_dd_mon_yyyy_hhmmss_SSS.matcher(input);
2424       if (matcher.matches()) {
2425         day = intValue(matcher.group(1));
2426         month = monthInt(matcher.group(2));
2427         year = intValue(matcher.group(3));
2428         hour = intValue(matcher.group(4));
2429         minute = intValue(matcher.group(5));
2430         second = intValue(matcher.group(6));
2431         milli = intValue(matcher.group(7));
2432         foundMatch = true;
2433       }
2434     }
2435 
2436     Calendar calendar = Calendar.getInstance();
2437     calendar.set(Calendar.YEAR, year);
2438     calendar.set(Calendar.MONTH, month - 1);
2439     calendar.set(Calendar.DAY_OF_MONTH, day);
2440     calendar.set(Calendar.HOUR_OF_DAY, hour);
2441     calendar.set(Calendar.MINUTE, minute);
2442     calendar.set(Calendar.SECOND, second);
2443     calendar.set(Calendar.MILLISECOND, milli);
2444     return calendar.getTime();
2445   }
2446 
2447   /**
2448    * convert a month string to an int (1 indexed).
2449    * e.g. if input is feb or Feb or february or February return 2
2450    * @param mon
2451    * @return the month
2452    */
2453   public static int monthInt(String mon) {
2454 
2455     if (!isBlank(mon)) {
2456       mon = mon.toLowerCase();
2457 
2458       if (equals(mon, "jan") || equals(mon, "january")) {
2459         return 1;
2460       }
2461 
2462       if (equals(mon, "feb") || equals(mon, "february")) {
2463         return 2;
2464       }
2465 
2466       if (equals(mon, "mar") || equals(mon, "march")) {
2467         return 3;
2468       }
2469 
2470       if (equals(mon, "apr") || equals(mon, "april")) {
2471         return 4;
2472       }
2473 
2474       if (equals(mon, "may")) {
2475         return 5;
2476       }
2477 
2478       if (equals(mon, "jun") || equals(mon, "june")) {
2479         return 6;
2480       }
2481 
2482       if (equals(mon, "jul") || equals(mon, "july")) {
2483         return 7;
2484       }
2485 
2486       if (equals(mon, "aug") || equals(mon, "august")) {
2487         return 8;
2488       }
2489 
2490       if (equals(mon, "sep") || equals(mon, "september")) {
2491         return 9;
2492       }
2493 
2494       if (equals(mon, "oct") || equals(mon, "october")) {
2495         return 10;
2496       }
2497 
2498       if (equals(mon, "nov") || equals(mon, "november")) {
2499         return 11;
2500       }
2501 
2502       if (equals(mon, "dec") || equals(mon, "december")) {
2503         return 12;
2504       }
2505 
2506     }
2507 
2508     throw new RuntimeException("Invalid month: " + mon);
2509   }
2510 
2511   /**
2512    * See if the input is null or if string, if it is empty or blank (whitespace)
2513    * @param input
2514    * @return true if blank
2515    */
2516   public static boolean isBlank(Object input) {
2517     if (null == input) {
2518       return true;
2519     }
2520     return (input instanceof String && isBlank((String) input));
2521   }
2522 
2523   /**
2524    * see if a class is a scalar (not bean, not array or list, etc)
2525    * @param type
2526    * @return true if scalar
2527    */
2528   public static boolean isScalar(Class<?> type) {
2529 
2530     if (type.isArray()) {
2531       return false;
2532     }
2533 
2534     //definitely all primitives
2535     if (type.isPrimitive()) {
2536       return true;
2537     }
2538     //Integer, Float, etc
2539     if (Number.class.isAssignableFrom(type)) {
2540       return true;
2541     }
2542     //Date, Timestamp
2543     if (Date.class.isAssignableFrom(type)) {
2544       return true;
2545     }
2546     if (Character.class.equals(type)) {
2547       return true;
2548     }
2549     //handles strings and string builders
2550     if (CharSequence.class.equals(type) || CharSequence.class.isAssignableFrom(type)) {
2551       return true;
2552     }
2553     if (Class.class == type || Boolean.class == type || type.isEnum()) {
2554       return true;
2555     }
2556     //appears not to be a scalar
2557     return false;
2558   }
2559 
2560   /**
2561    * <pre>
2562    * Convert a string or object to a timestamp (could be string, date, timestamp, etc)
2563    * yyyymmdd
2564    * or 
2565    * yyyy/MM/dd
2566    * or
2567    * yyyy/MM/dd HH:mm:ss
2568    * or
2569    * yyyy/MM/dd HH:mm:ss.SSS
2570    * or
2571    * yyyy/MM/dd HH:mm:ss.SSSSSS
2572    * 
2573    * </pre>
2574    * 
2575    * @param input
2576    * @return the timestamp 
2577    * @throws RuntimeException if invalid format
2578    */
2579   public static Timestamp toTimestamp(Object input) {
2580 
2581     if (null == input) {
2582       return null;
2583     } else if (input instanceof java.sql.Timestamp) {
2584       return (Timestamp) input;
2585     } else if (input instanceof String) {
2586       return stringToTimestamp((String) input);
2587     } else if (input instanceof Date) {
2588       return new Timestamp(((Date) input).getTime());
2589     } else if (input instanceof java.sql.Date) {
2590       return new Timestamp(((java.sql.Date) input).getTime());
2591     } else {
2592       throw new RuntimeException("Cannot convert Object to timestamp : " + input);
2593     }
2594 
2595   }
2596 
2597   /**
2598    * convert an object to a string
2599    * 
2600    * @param input
2601    *          is the object to convert
2602    * 
2603    * @return the String conversion of the object
2604    */
2605   public static String stringValue(Object input) {
2606     //this isnt needed
2607     if (input == null) {
2608       return (String) input;
2609     }
2610 
2611     if (input instanceof Timestamp) {
2612       //convert to yyyy/MM/dd HH:mm:ss.SSS
2613       return timestampToString((Timestamp) input);
2614     }
2615 
2616     if (input instanceof Date) {
2617       //convert to yyyymmdd
2618       return stringValue((Date) input);
2619     }
2620 
2621     if (input instanceof Number) {
2622       DecimalFormat decimalFormat = new DecimalFormat(
2623           "###################.###############");
2624       return decimalFormat.format(((Number) input).doubleValue());
2625 
2626     }
2627 
2628     return input.toString();
2629   }
2630 
2631   /**
2632    * Convert a timestamp into a string: yyyy/MM/dd HH:mm:ss.SSS
2633    * @param timestamp
2634    * @return the string representation
2635    */
2636   public synchronized static String timestampToString(Date timestamp) {
2637     if (timestamp == null) {
2638       return null;
2639     }
2640     return timestampFormat.format(timestamp);
2641   }
2642 
2643   /**
2644    * Convert a timestamp into a string: yyyy/MM/dd HH:mm:ss.SSS
2645    * @param timestamp
2646    * @return the string representation
2647    */
2648   public synchronized static String timestampToFileString(Date timestamp) {
2649     if (timestamp == null) {
2650       return null;
2651     }
2652     return timestampFileFormat.format(timestamp);
2653   }
2654 
2655   /**
2656    * get the timestamp format for this thread
2657    * if you call this make sure to synchronize on FastDateUtils.class
2658    * @return the timestamp format
2659    */
2660   synchronized static SimpleDateFormat dateFormat() {
2661     return dateFormat;
2662   }
2663 
2664   /**
2665    * get the timestamp format for this thread
2666    * if you call this make sure to synchronize on FastDateUtils.class
2667    * @return the timestamp format
2668    */
2669   synchronized static SimpleDateFormat dateFormat2() {
2670     return dateFormat2;
2671   }
2672 
2673   /**
2674    * convert a date to the standard string yyyy/mm/dd
2675    * @param date 
2676    * @return the string value
2677    */
2678   public static String stringValue(java.util.Date date) {
2679     synchronized (GcElUtilsSafe.class) {
2680       if (date == null) {
2681         return null;
2682       }
2683 
2684       //lets see if there is a time component
2685       Calendar calendar = new GregorianCalendar();
2686       calendar.setTime(date);
2687       if (calendar.get(Calendar.HOUR_OF_DAY) == 0
2688           && calendar.get(Calendar.MINUTE) == 0
2689           && calendar.get(Calendar.SECOND) == 0
2690           && calendar.get(Calendar.MILLISECOND) == 0) {
2691 
2692         String theString = dateFormat2().format(date);
2693 
2694         return theString;
2695         
2696       }
2697       
2698       //do a timestamp format
2699       return timestampToString(date);
2700       
2701     }
2702   }
2703 
2704   /**
2705    * <pre>convert a string to timestamp based on the following formats:
2706    * yyyyMMdd
2707    * yyyy/MM/dd
2708    * yyyy/MM/dd HH:mm:ss
2709    * yyyy/MM/dd HH:mm:ss.SSS
2710    * yyyy/MM/dd HH:mm:ss.SSSSSS
2711    * </pre>
2712    * @param input
2713    * @return the timestamp object
2714    */
2715   public static Timestamp stringToTimestamp(String input) {
2716     Date date = stringToTimestampHelper(input);
2717     if (date == null) {
2718       return null;
2719     }
2720     //maybe already a timestamp
2721     if (date instanceof Timestamp) {
2722       return (Timestamp) date;
2723     }
2724     return new Timestamp(date.getTime());
2725   }
2726 
2727   /**
2728    * return a date based on input, null safe.  Allow any of the three 
2729    * formats:
2730    * yyyyMMdd
2731    * yyyy/MM/dd
2732    * yyyy/MM/dd HH:mm:ss
2733    * yyyy/MM/dd HH:mm:ss.SSS
2734    * yyyy/MM/dd HH:mm:ss.SSSSSS
2735    * 
2736    * @param input
2737    * @return the millis, -1 for null
2738    */
2739   synchronized static Date stringToTimestampHelper(String input) {
2740     //trim and handle null and empty
2741     if (isBlank(input)) {
2742       return null;
2743     }
2744     input = input.trim();
2745     try {
2746       //convert mainframe
2747       if (equals("99999999", input)
2748           || equals("999999", input)) {
2749         input = "20991231";
2750       }
2751       if (input.length() == 8) {
2752 
2753         return dateFormat().parse(input);
2754       }
2755       if (input.length() == 10) {
2756 
2757         return dateFormat2().parse(input);
2758       }
2759       if (!contains(input, '.')) {
2760         if (contains(input, '/')) {
2761           return dateMinutesSecondsFormat.parse(input);
2762         }
2763         //else no slash
2764         return dateMinutesSecondsNoSlashFormat.parse(input);
2765       }
2766       if (contains(input, '/')) {
2767         //see if the period is 6 back
2768         int lastDotIndex = input.lastIndexOf('.');
2769         if (lastDotIndex == input.length() - 7) {
2770           String nonNanoInput = input.substring(0, input.length() - 3);
2771           Date date = timestampFormat.parse(nonNanoInput);
2772           //get the last 3
2773           String lastThree = input.substring(input.length() - 3, input.length());
2774           int lastThreeInt = Integer.parseInt(lastThree);
2775           Timestamp timestamp = new Timestamp(date.getTime());
2776           timestamp.setNanos(timestamp.getNanos() + (lastThreeInt * 1000));
2777           return timestamp;
2778         }
2779         return timestampFormat.parse(input);
2780       }
2781       //else no slash
2782       return timestampNoSlashFormat.parse(input);
2783     } catch (ParseException pe) {
2784       throw new RuntimeException(errorStart + input);
2785     }
2786   }
2787 
2788   /**
2789    * start of error parsing messages
2790    */
2791   private static final String errorStart = "Invalid timestamp, please use any of the formats: "
2792       + DATE_FORMAT + ", " + TIMESTAMP_FORMAT
2793       + ", " + DATE_MINUTES_SECONDS_FORMAT + ": ";
2794 
2795   /**
2796    * Convert an object to a byte, allow nulls
2797    * @param input
2798    * @return the boolean object value
2799    */
2800   public static BigDecimal bigDecimalObjectValue(Object input) {
2801     if (input instanceof BigDecimal) {
2802       return (BigDecimal) input;
2803     }
2804     if (isBlank(input)) {
2805       return null;
2806     }
2807     return BigDecimal.valueOf(doubleValue(input));
2808   }
2809 
2810   /**
2811    * Convert an object to a byte, allow nulls
2812    * @param input
2813    * @return the boolean object value
2814    */
2815   public static Byte byteObjectValue(Object input) {
2816     if (input instanceof Byte) {
2817       return (Byte) input;
2818     }
2819     if (isBlank(input)) {
2820       return null;
2821     }
2822     return Byte.valueOf(byteValue(input));
2823   }
2824 
2825   /**
2826    * convert an object to a byte
2827    * @param input
2828    * @return the byte
2829    */
2830   public static byte byteValue(Object input) {
2831     if (input instanceof String) {
2832       String string = (String) input;
2833       return Byte.parseByte(string);
2834     }
2835     if (input instanceof Number) {
2836       return ((Number) input).byteValue();
2837     }
2838     throw new RuntimeException("Cannot convert to byte: " + className(input));
2839   }
2840 
2841   /**
2842    * get the Double value of an object
2843    * 
2844    * @param input
2845    *          is a number or String
2846    * @param allowNullBlank used to default to false, if true, return null if nul inputted 
2847    * 
2848    * @return the Double equivalent
2849    */
2850   public static Double doubleObjectValue(Object input, boolean allowNullBlank) {
2851 
2852     if (input instanceof Double) {
2853       return (Double) input;
2854     }
2855 
2856     if (allowNullBlank && isBlank(input)) {
2857       return null;
2858     }
2859 
2860     return Double.valueOf(doubleValue(input));
2861   }
2862 
2863   /**
2864    * get the double value of an object
2865    * 
2866    * @param input
2867    *          is a number or String
2868    * 
2869    * @return the double equivalent
2870    */
2871   public static double doubleValue(Object input) {
2872     if (input instanceof String) {
2873       String string = (String) input;
2874       return Double.parseDouble(string);
2875     }
2876     if (input instanceof Number) {
2877       return ((Number) input).doubleValue();
2878     }
2879     throw new RuntimeException("Cannot convert to double: " + className(input));
2880   }
2881 
2882   /**
2883    * get the double value of an object, do not throw an 
2884    * exception if there is an
2885    * error
2886    * 
2887    * @param input
2888    *          is a number or String
2889    * 
2890    * @return the double equivalent
2891    */
2892   public static double doubleValueNoError(Object input) {
2893     if (input == null || (input instanceof String
2894         && isBlank((String) input))) {
2895       return NOT_FOUND;
2896     }
2897 
2898     try {
2899       return doubleValue(input);
2900     } catch (Exception e) {
2901       //no need to log here
2902     }
2903 
2904     return NOT_FOUND;
2905   }
2906 
2907   /**
2908    * get the Float value of an object
2909    * 
2910    * @param input
2911    *          is a number or String
2912    * @param allowNullBlank true if allow null or blank
2913    * 
2914    * @return the Float equivalent
2915    */
2916   public static Float floatObjectValue(Object input, boolean allowNullBlank) {
2917 
2918     if (input instanceof Float) {
2919       return (Float) input;
2920     }
2921 
2922     if (allowNullBlank && isBlank(input)) {
2923       return null;
2924     }
2925     return Float.valueOf(floatValue(input));
2926   }
2927 
2928   /**
2929    * get the float value of an object
2930    * 
2931    * @param input
2932    *          is a number or String
2933    * 
2934    * @return the float equivalent
2935    */
2936   public static float floatValue(Object input) {
2937     if (input instanceof String) {
2938       String string = (String) input;
2939       return Float.parseFloat(string);
2940     }
2941     if (input instanceof Number) {
2942       return ((Number) input).floatValue();
2943     }
2944     throw new RuntimeException("Cannot convert to float: " + className(input));
2945   }
2946 
2947   /**
2948    * get the float value of an object, do not throw an exception if there is an
2949    * error
2950    * 
2951    * @param input
2952    *          is a number or String
2953    * 
2954    * @return the float equivalent
2955    */
2956   public static float floatValueNoError(Object input) {
2957     if (input == null || (input instanceof String
2958         && isBlank((String) input))) {
2959       return NOT_FOUND;
2960     }
2961     try {
2962       return floatValue(input);
2963     } catch (Exception e) {
2964       LOG.error(e);
2965     }
2966 
2967     return NOT_FOUND;
2968   }
2969 
2970   /**
2971    * get the Integer value of an object
2972    * 
2973    * @param input
2974    *          is a number or String
2975    * @param allowNullBlank true if convert null or blank to null
2976    * 
2977    * @return the Integer equivalent
2978    */
2979   public static Integer intObjectValue(Object input, boolean allowNullBlank) {
2980 
2981     if (input instanceof Integer) {
2982       return (Integer) input;
2983     }
2984 
2985     if (allowNullBlank && isBlank(input)) {
2986       return null;
2987     }
2988 
2989     return Integer.valueOf(intValue(input));
2990   }
2991 
2992   /**
2993    * convert an object to a int
2994    * @param input
2995    * @return the number
2996    */
2997   public static int intValue(Object input) {
2998     if (input instanceof String) {
2999       String string = (String) input;
3000       return Integer.parseInt(string);
3001     }
3002     if (input instanceof Number) {
3003       return ((Number) input).intValue();
3004     }
3005     throw new RuntimeException("Cannot convert to int: " + className(input));
3006   }
3007 
3008   /**
3009    * convert an object to a int
3010    * @param input
3011    * @param valueIfNull is if the input is null or empty, return this value
3012    * @return the number
3013    */
3014   public static int intValue(Object input, int valueIfNull) {
3015     if (input == null || "".equals(input)) {
3016       return valueIfNull;
3017     }
3018     return intObjectValue(input, false);
3019   }
3020 
3021   /**
3022    * get the int value of an object, do not throw an exception if there is an
3023    * error
3024    * 
3025    * @param input
3026    *          is a number or String
3027    * 
3028    * @return the int equivalent
3029    */
3030   public static int intValueNoError(Object input) {
3031     if (input == null || (input instanceof String
3032         && isBlank((String) input))) {
3033       return NOT_FOUND;
3034     }
3035     try {
3036       return intValue(input);
3037     } catch (Exception e) {
3038       //no need to log here
3039     }
3040 
3041     return NOT_FOUND;
3042   }
3043 
3044   /** special number when a number is not found */
3045   public static final int NOT_FOUND = -999999999;
3046 
3047   /**
3048    * logger
3049    */
3050   private static Log LOG = GrouperClientUtils.retrieveLog(GcElUtilsSafe.class);
3051 
3052   /**
3053    * The name says it all.
3054    */
3055   public static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
3056 
3057   /**
3058    * get the Long value of an object
3059    * 
3060    * @param input
3061    *          is a number or String
3062    * @param allowNullBlank true if null or blank converts to null
3063    * 
3064    * @return the Long equivalent
3065    */
3066   public static Long longObjectValue(Object input, boolean allowNullBlank) {
3067 
3068     if (input instanceof Long) {
3069       return (Long) input;
3070     }
3071 
3072     if (allowNullBlank && isBlank(input)) {
3073       return null;
3074     }
3075 
3076     return Long.valueOf(longValue(input));
3077   }
3078 
3079   /**
3080    * convert an object to a long
3081    * @param input
3082    * @return the number
3083    */
3084   public static long longValue(Object input) {
3085     if (input instanceof String) {
3086       String string = (String) input;
3087       return Long.parseLong(string);
3088     }
3089     if (input instanceof Number) {
3090       return ((Number) input).longValue();
3091     }
3092     throw new RuntimeException("Cannot convert to long: " + className(input));
3093   }
3094 
3095   /**
3096    * convert an object to a long
3097    * @param input
3098    * @param valueIfNull is if the input is null or empty, return this value
3099    * @return the number
3100    */
3101   public static long longValue(Object input, long valueIfNull) {
3102     if (input == null || "".equals(input)) {
3103       return valueIfNull;
3104     }
3105     return longObjectValue(input, false);
3106   }
3107 
3108   /**
3109    * get the long value of an object, do not throw an exception if there is an
3110    * error
3111    * 
3112    * @param input
3113    *          is a number or String
3114    * 
3115    * @return the long equivalent
3116    */
3117   public static long longValueNoError(Object input) {
3118     if (input == null || (input instanceof String
3119         && isBlank((String) input))) {
3120       return NOT_FOUND;
3121     }
3122     try {
3123       return longValue(input);
3124     } catch (Exception e) {
3125       //no need to log here
3126     }
3127 
3128     return NOT_FOUND;
3129   }
3130 
3131   /**
3132    * get the Short value of an object.  converts null or blank to null
3133    * 
3134    * @param input
3135    *          is a number or String
3136    * 
3137    * @return the Long equivalent
3138    */
3139   public static Short shortObjectValue(Object input) {
3140 
3141     if (input instanceof Short) {
3142       return (Short) input;
3143     }
3144 
3145     if (isBlank(input)) {
3146       return null;
3147     }
3148 
3149     return Short.valueOf(shortValue(input));
3150   }
3151 
3152   /**
3153    * convert an object to a short
3154    * @param input
3155    * @return the number
3156    */
3157   public static short shortValue(Object input) {
3158     if (input instanceof String) {
3159       String string = (String) input;
3160       return Short.parseShort(string);
3161     }
3162     if (input instanceof Number) {
3163       return ((Number) input).shortValue();
3164     }
3165     throw new RuntimeException("Cannot convert to short: " + className(input));
3166   }
3167 
3168   /**
3169    * get the Character wrapper value for the input
3170    * @param input allow null, return null
3171    * @return the Character object wrapper
3172    */
3173   public static Character charObjectValue(Object input) {
3174     if (input instanceof Character) {
3175       return (Character) input;
3176     }
3177     if (isBlank(input)) {
3178       return null;
3179     }
3180     return new Character(charValue(input));
3181   }
3182 
3183   /**
3184    * convert an object to a char
3185    * @param input
3186    * @return the number
3187    */
3188   public static char charValue(Object input) {
3189     if (input instanceof Character) {
3190       return ((Character) input).charValue();
3191     }
3192     //if string length 1, thats ok
3193     if (input instanceof String) {
3194       String inputString = (String) input;
3195       if (inputString.length() == 1) {
3196         return inputString.charAt(0);
3197       }
3198     }
3199     throw new RuntimeException("Cannot convert to char: "
3200         + (input == null ? null : (input.getClass() + ", " + input)));
3201   }
3202 
3203   /**
3204    * replace all whitespace with space
3205    * @param input
3206    * @return the string
3207    */
3208   public static String replaceWhitespaceWithSpace(String input) {
3209     if (input == null) {
3210       return input;
3211     }
3212     return input.replaceAll("\\s+", " ");
3213   }
3214 
3215   /**
3216    * this method takes a long (less than 62) and converts it to a 1 character
3217    * string (a-z, A-Z, 0-9)
3218    * 
3219    * @param theLong
3220    *          is the long (less than 62) to convert to a 1 character string
3221    * 
3222    * @return a one character string
3223    */
3224   public static String convertLongToChar(long theLong) {
3225     if ((theLong < 0) || (theLong >= 62)) {
3226       throw new RuntimeException("convertLongToChar() "
3227           + " invalid input (not >=0 && <62: " + theLong);
3228     } else if (theLong < 26) {
3229       return "" + (char) ('a' + theLong);
3230     } else if (theLong < 52) {
3231       return "" + (char) ('A' + (theLong - 26));
3232     } else {
3233       return "" + (char) ('0' + (theLong - 52));
3234     }
3235   }
3236 
3237   /**
3238    * this method takes a long (less than 36) and converts it to a 1 character
3239    * string (A-Z, 0-9)
3240    * 
3241    * @param theLong
3242    *          is the long (less than 36) to convert to a 1 character string
3243    * 
3244    * @return a one character string
3245    */
3246   public static String convertLongToCharSmall(long theLong) {
3247     if ((theLong < 0) || (theLong >= 36)) {
3248       throw new RuntimeException("convertLongToCharSmall() "
3249           + " invalid input (not >=0 && <36: " + theLong);
3250     } else if (theLong < 26) {
3251       return "" + (char) ('A' + theLong);
3252     } else {
3253       return "" + (char) ('0' + (theLong - 26));
3254     }
3255   }
3256 
3257   /**
3258    * convert a long to a string by converting it to base 62 (26 lower, 26 upper,
3259    * 10 digits)
3260    * 
3261    * @param theLong
3262    *          is the long to convert
3263    * 
3264    * @return the String conversion of this
3265    */
3266   public static String convertLongToString(long theLong) {
3267     long quotient = theLong / 62;
3268     long remainder = theLong % 62;
3269 
3270     if (quotient == 0) {
3271       return convertLongToChar(remainder);
3272     }
3273     StringBuffer result = new StringBuffer();
3274     result.append(convertLongToString(quotient));
3275     result.append(convertLongToChar(remainder));
3276 
3277     return result.toString();
3278   }
3279 
3280   /**
3281    * convert a long to a string by converting it to base 36 (26 upper, 10
3282    * digits)
3283    * 
3284    * @param theLong
3285    *          is the long to convert
3286    * 
3287    * @return the String conversion of this
3288    */
3289   public static String convertLongToStringSmall(long theLong) {
3290     long quotient = theLong / 36;
3291     long remainder = theLong % 36;
3292 
3293     if (quotient == 0) {
3294       return convertLongToCharSmall(remainder);
3295     }
3296     StringBuffer result = new StringBuffer();
3297     result.append(convertLongToStringSmall(quotient));
3298     result.append(convertLongToCharSmall(remainder));
3299 
3300     return result.toString();
3301   }
3302 
3303   /**
3304    * increment a character (A-Z then 0-9)
3305    * 
3306    * @param theChar
3307    * 
3308    * @return the value
3309    */
3310   public static char incrementChar(char theChar) {
3311     if (theChar == 'Z') {
3312       return '0';
3313     }
3314 
3315     if (theChar == '9') {
3316       return 'A';
3317     }
3318 
3319     return ++theChar;
3320   }
3321 
3322   /**
3323    * Increment a string with A-Z and 0-9 (no lower case so case insensitive apps
3324    * like windows IE will still work)
3325    * 
3326    * @param string
3327    * 
3328    * @return the value
3329    */
3330   public static char[] incrementStringInt(char[] string) {
3331     if (string == null) {
3332       return string;
3333     }
3334 
3335     //loop through the string backwards
3336     int i = 0;
3337 
3338     for (i = string.length - 1; i >= 0; i--) {
3339       char inc = string[i];
3340       inc = incrementChar(inc);
3341       string[i] = inc;
3342 
3343       if (inc != 'A') {
3344         break;
3345       }
3346     }
3347 
3348     //if we are at 0, then it means we hit AAAAAAA (or more)
3349     if (i < 0) {
3350       return ("A" + new String(string)).toCharArray();
3351     }
3352 
3353     return string;
3354   }
3355 
3356   /**
3357    * is ascii char
3358    * @param input
3359    * @return true if ascii
3360    */
3361   public static boolean isAscii(char input) {
3362     return input < 128;
3363   }
3364 
3365   /**
3366    * find the length of ascii chars (non ascii are counted as two)
3367    * @param input
3368    * @return the length of ascii chars
3369    */
3370   public static int lengthAscii(String input) {
3371     if (input == null) {
3372       return 0;
3373     }
3374     //see what real length is
3375     int utfLength = input.length();
3376     //count how many non asciis
3377     int extras = 0;
3378     for (int i = 0; i < utfLength; i++) {
3379       //keep count of non ascii chars
3380       if (!isAscii(input.charAt(i))) {
3381         extras++;
3382       }
3383     }
3384     return utfLength + extras;
3385   }
3386 
3387   /**
3388    * string length
3389    * @param string
3390    * @return string length
3391    */
3392   public static int stringLength(String string) {
3393     return string == null ? 0 : string.length();
3394   }
3395 
3396   /**
3397    * find the length of ascii chars (non ascii are counted as two)
3398    * @param input is the string to operate on
3399    * @param requiredLength length we need the string to be
3400    * @return the length of ascii chars
3401    */
3402   public static String truncateAscii(String input, int requiredLength) {
3403     if (input == null) {
3404       return input;
3405     }
3406     //see what real length is
3407     int utfLength = input.length();
3408 
3409     //see if not worth checking
3410     if (utfLength * 2 < requiredLength) {
3411       return input;
3412     }
3413 
3414     //count how many non asciis
3415     int asciiLength = 0;
3416     for (int i = 0; i < utfLength; i++) {
3417 
3418       asciiLength++;
3419 
3420       //keep count of non ascii chars
3421       if (!isAscii(input.charAt(i))) {
3422         asciiLength++;
3423       }
3424 
3425       //see if we are over 
3426       if (asciiLength > requiredLength) {
3427         //do not include the current char
3428         return input.substring(0, i);
3429       }
3430     }
3431     //must have fit
3432     return input;
3433   }
3434 
3435   /**
3436    * null safe string compare
3437    * @param first
3438    * @param second
3439    * @return true if equal
3440    */
3441   public static boolean equals(String first, String second) {
3442     if (first == second) {
3443       return true;
3444     }
3445     if (first == null || second == null) {
3446       return false;
3447     }
3448     return first.equals(second);
3449   }
3450 
3451   /**
3452    * <p>Checks if a String is whitespace, empty ("") or null.</p>
3453    *
3454    * <pre>
3455    * isBlank(null)      = true
3456    * isBlank("")        = true
3457    * isBlank(" ")       = true
3458    * isBlank("bob")     = false
3459    * isBlank("  bob  ") = false
3460    * </pre>
3461    *
3462    * @param str  the String to check, may be null
3463    * @return <code>true</code> if the String is null, empty or whitespace
3464    * @since 2.0
3465    */
3466   public static boolean isBlank(String str) {
3467     int strLen;
3468     if (str == null || (strLen = str.length()) == 0) {
3469       return true;
3470     }
3471     for (int i = 0; i < strLen; i++) {
3472       if ((Character.isWhitespace(str.charAt(i)) == false)) {
3473         return false;
3474       }
3475     }
3476     return true;
3477   }
3478 
3479   /**
3480    * 
3481    * @param str
3482    * @return true if not blank
3483    */
3484   public static boolean isNotBlank(String str) {
3485     return !isBlank(str);
3486   }
3487 
3488   /**
3489    * trim whitespace from string
3490    * @param str
3491    * @return trimmed string
3492    */
3493   public static String trim(String str) {
3494     return str == null ? null : str.trim();
3495   }
3496 
3497   /**
3498    * equalsignorecase
3499    * @param str1
3500    * @param str2
3501    * @return true if the strings are equal ignore case
3502    */
3503   public static boolean equalsIgnoreCase(String str1, String str2) {
3504     return str1 == null ? str2 == null : str1.equalsIgnoreCase(str2);
3505   }
3506 
3507   /**
3508    * trim to empty, convert null to empty
3509    * @param str
3510    * @return trimmed
3511    */
3512   public static String trimToEmpty(String str) {
3513     return str == null ? "" : str.trim();
3514   }
3515 
3516   /**
3517    * <p>Abbreviates a String using ellipses. This will turn
3518    * "Now is the time for all good men" into "Now is the time for..."</p>
3519    *
3520    * <p>Specifically:
3521    * <ul>
3522    *   <li>If <code>str</code> is less than <code>maxWidth</code> characters
3523    *       long, return it.</li>
3524    *   <li>Else abbreviate it to <code>(substring(str, 0, max-3) + "...")</code>.</li>
3525    *   <li>If <code>maxWidth</code> is less than <code>4</code>, throw an
3526    *       <code>IllegalArgumentException</code>.</li>
3527    *   <li>In no case will it return a String of length greater than
3528    *       <code>maxWidth</code>.</li>
3529    * </ul>
3530    * </p>
3531    *
3532    * <pre>
3533    * StringUtils.abbreviate(null, *)      = null
3534    * StringUtils.abbreviate("", 4)        = ""
3535    * StringUtils.abbreviate("abcdefg", 6) = "abc..."
3536    * StringUtils.abbreviate("abcdefg", 7) = "abcdefg"
3537    * StringUtils.abbreviate("abcdefg", 8) = "abcdefg"
3538    * StringUtils.abbreviate("abcdefg", 4) = "a..."
3539    * StringUtils.abbreviate("abcdefg", 3) = IllegalArgumentException
3540    * </pre>
3541    *
3542    * @param str  the String to check, may be null
3543    * @param maxWidth  maximum length of result String, must be at least 4
3544    * @return abbreviated String, <code>null</code> if null String input
3545    * @throws IllegalArgumentException if the width is too small
3546    * @since 2.0
3547    */
3548   public static String abbreviate(String str, int maxWidth) {
3549     return abbreviate(str, 0, maxWidth);
3550   }
3551 
3552   /**
3553    * <p>Abbreviates a String using ellipses. This will turn
3554    * "Now is the time for all good men" into "...is the time for..."</p>
3555    *
3556    * <p>Works like <code>abbreviate(String, int)</code>, but allows you to specify
3557    * a "left edge" offset.  Note that this left edge is not necessarily going to
3558    * be the leftmost character in the result, or the first character following the
3559    * ellipses, but it will appear somewhere in the result.
3560    *
3561    * <p>In no case will it return a String of length greater than
3562    * <code>maxWidth</code>.</p>
3563    *
3564    * <pre>
3565    * StringUtils.abbreviate(null, *, *)                = null
3566    * StringUtils.abbreviate("", 0, 4)                  = ""
3567    * StringUtils.abbreviate("abcdefghijklmno", -1, 10) = "abcdefg..."
3568    * StringUtils.abbreviate("abcdefghijklmno", 0, 10)  = "abcdefg..."
3569    * StringUtils.abbreviate("abcdefghijklmno", 1, 10)  = "abcdefg..."
3570    * StringUtils.abbreviate("abcdefghijklmno", 4, 10)  = "abcdefg..."
3571    * StringUtils.abbreviate("abcdefghijklmno", 5, 10)  = "...fghi..."
3572    * StringUtils.abbreviate("abcdefghijklmno", 6, 10)  = "...ghij..."
3573    * StringUtils.abbreviate("abcdefghijklmno", 8, 10)  = "...ijklmno"
3574    * StringUtils.abbreviate("abcdefghijklmno", 10, 10) = "...ijklmno"
3575    * StringUtils.abbreviate("abcdefghijklmno", 12, 10) = "...ijklmno"
3576    * StringUtils.abbreviate("abcdefghij", 0, 3)        = IllegalArgumentException
3577    * StringUtils.abbreviate("abcdefghij", 5, 6)        = IllegalArgumentException
3578    * </pre>
3579    *
3580    * @param str  the String to check, may be null
3581    * @param offset  left edge of source String
3582    * @param maxWidth  maximum length of result String, must be at least 4
3583    * @return abbreviated String, <code>null</code> if null String input
3584    * @throws IllegalArgumentException if the width is too small
3585    * @since 2.0
3586    */
3587   public static String abbreviate(String str, int offset, int maxWidth) {
3588     if (str == null) {
3589       return null;
3590     }
3591     if (maxWidth < 4) {
3592       throw new IllegalArgumentException("Minimum abbreviation width is 4");
3593     }
3594     if (str.length() <= maxWidth) {
3595       return str;
3596     }
3597     if (offset > str.length()) {
3598       offset = str.length();
3599     }
3600     if ((str.length() - offset) < (maxWidth - 3)) {
3601       offset = str.length() - (maxWidth - 3);
3602     }
3603     if (offset <= 4) {
3604       return str.substring(0, maxWidth - 3) + "...";
3605     }
3606     if (maxWidth < 7) {
3607       throw new IllegalArgumentException("Minimum abbreviation width with offset is 7");
3608     }
3609     if ((offset + (maxWidth - 3)) < str.length()) {
3610       return "..." + abbreviate(str.substring(offset), maxWidth - 3);
3611     }
3612     return "..." + str.substring(str.length() - (maxWidth - 3));
3613   }
3614 
3615   // Splitting
3616   //-----------------------------------------------------------------------
3617   /**
3618    * <p>Splits the provided text into an array, using whitespace as the
3619    * separator.
3620    * Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
3621    *
3622    * <p>The separator is not included in the returned String array.
3623    * Adjacent separators are treated as one separator.
3624    * For more control over the split use the StrTokenizer class.</p>
3625    *
3626    * <p>A <code>null</code> input String returns <code>null</code>.</p>
3627    *
3628    * <pre>
3629    * StringUtils.split(null)       = null
3630    * StringUtils.split("")         = []
3631    * StringUtils.split("abc def")  = ["abc", "def"]
3632    * StringUtils.split("abc  def") = ["abc", "def"]
3633    * StringUtils.split(" abc ")    = ["abc"]
3634    * </pre>
3635    *
3636    * @param str  the String to parse, may be null
3637    * @return an array of parsed Strings, <code>null</code> if null String input
3638    */
3639   public static String[] split(String str) {
3640     return split(str, null, -1);
3641   }
3642 
3643   /**
3644    * <p>Splits the provided text into an array, separator specified.
3645    * This is an alternative to using StringTokenizer.</p>
3646    *
3647    * <p>The separator is not included in the returned String array.
3648    * Adjacent separators are treated as one separator.
3649    * For more control over the split use the StrTokenizer class.</p>
3650    *
3651    * <p>A <code>null</code> input String returns <code>null</code>.</p>
3652    *
3653    * <pre>
3654    * StringUtils.split(null, *)         = null
3655    * StringUtils.split("", *)           = []
3656    * StringUtils.split("a.b.c", '.')    = ["a", "b", "c"]
3657    * StringUtils.split("a..b.c", '.')   = ["a", "b", "c"]
3658    * StringUtils.split("a:b:c", '.')    = ["a:b:c"]
3659    * StringUtils.split("a\tb\nc", null) = ["a", "b", "c"]
3660    * StringUtils.split("a b c", ' ')    = ["a", "b", "c"]
3661    * </pre>
3662    *
3663    * @param str  the String to parse, may be null
3664    * @param separatorChar  the character used as the delimiter,
3665    *  <code>null</code> splits on whitespace
3666    * @return an array of parsed Strings, <code>null</code> if null String input
3667    * @since 2.0
3668    */
3669   public static String[] split(String str, char separatorChar) {
3670     return splitWorker(str, separatorChar, false);
3671   }
3672 
3673   /**
3674    * <p>Splits the provided text into an array, separators specified.
3675    * This is an alternative to using StringTokenizer.</p>
3676    *
3677    * <p>The separator is not included in the returned String array.
3678    * Adjacent separators are treated as one separator.
3679    * For more control over the split use the StrTokenizer class.</p>
3680    *
3681    * <p>A <code>null</code> input String returns <code>null</code>.
3682    * A <code>null</code> separatorChars splits on whitespace.</p>
3683    *
3684    * <pre>
3685    * StringUtils.split(null, *)         = null
3686    * StringUtils.split("", *)           = []
3687    * StringUtils.split("abc def", null) = ["abc", "def"]
3688    * StringUtils.split("abc def", " ")  = ["abc", "def"]
3689    * StringUtils.split("abc  def", " ") = ["abc", "def"]
3690    * StringUtils.split("ab:cd:ef", ":") = ["ab", "cd", "ef"]
3691    * </pre>
3692    *
3693    * @param str  the String to parse, may be null
3694    * @param separatorChars  the characters used as the delimiters,
3695    *  <code>null</code> splits on whitespace
3696    * @return an array of parsed Strings, <code>null</code> if null String input
3697    */
3698   public static String[] split(String str, String separatorChars) {
3699     return splitWorker(str, separatorChars, -1, false);
3700   }
3701 
3702   /**
3703    * <p>Splits the provided text into an array with a maximum length,
3704    * separators specified.</p>
3705    *
3706    * <p>The separator is not included in the returned String array.
3707    * Adjacent separators are treated as one separator.</p>
3708    *
3709    * <p>A <code>null</code> input String returns <code>null</code>.
3710    * A <code>null</code> separatorChars splits on whitespace.</p>
3711    *
3712    * <p>If more than <code>max</code> delimited substrings are found, the last
3713    * returned string includes all characters after the first <code>max - 1</code>
3714    * returned strings (including separator characters).</p>
3715    *
3716    * <pre>
3717    * StringUtils.split(null, *, *)            = null
3718    * StringUtils.split("", *, *)              = []
3719    * StringUtils.split("ab de fg", null, 0)   = ["ab", "cd", "ef"]
3720    * StringUtils.split("ab   de fg", null, 0) = ["ab", "cd", "ef"]
3721    * StringUtils.split("ab:cd:ef", ":", 0)    = ["ab", "cd", "ef"]
3722    * StringUtils.split("ab:cd:ef", ":", 2)    = ["ab", "cd:ef"]
3723    * </pre>
3724    *
3725    * @param str  the String to parse, may be null
3726    * @param separatorChars  the characters used as the delimiters,
3727    *  <code>null</code> splits on whitespace
3728    * @param max  the maximum number of elements to include in the
3729    *  array. A zero or negative value implies no limit
3730    * @return an array of parsed Strings, <code>null</code> if null String input
3731    */
3732   public static String[] split(String str, String separatorChars, int max) {
3733     return splitWorker(str, separatorChars, max, false);
3734   }
3735 
3736   /**
3737    * <p>Splits the provided text into an array, separator string specified.</p>
3738    *
3739    * <p>The separator(s) will not be included in the returned String array.
3740    * Adjacent separators are treated as one separator.</p>
3741    *
3742    * <p>A <code>null</code> input String returns <code>null</code>.
3743    * A <code>null</code> separator splits on whitespace.</p>
3744    *
3745    * <pre>
3746    * StringUtils.split(null, *)            = null
3747    * StringUtils.split("", *)              = []
3748    * StringUtils.split("ab de fg", null)   = ["ab", "de", "fg"]
3749    * StringUtils.split("ab   de fg", null) = ["ab", "de", "fg"]
3750    * StringUtils.split("ab:cd:ef", ":")    = ["ab", "cd", "ef"]
3751    * StringUtils.split("abstemiouslyaeiouyabstemiously", "aeiouy")  = ["bst", "m", "sl", "bst", "m", "sl"]
3752    * StringUtils.split("abstemiouslyaeiouyabstemiously", "aeiouy")  = ["abstemiously", "abstemiously"]
3753    * </pre>
3754    *
3755    * @param str  the String to parse, may be null
3756    * @param separator  String containing the String to be used as a delimiter,
3757    *  <code>null</code> splits on whitespace
3758    * @return an array of parsed Strings, <code>null</code> if null String was input
3759    */
3760   public static String[] splitByWholeSeparator(String str, String separator) {
3761     return splitByWholeSeparator(str, separator, -1);
3762   }
3763 
3764   /**
3765    * <p>Splits the provided text into an array, separator string specified.
3766    * Returns a maximum of <code>max</code> substrings.</p>
3767    *
3768    * <p>The separator(s) will not be included in the returned String array.
3769    * Adjacent separators are treated as one separator.</p>
3770    *
3771    * <p>A <code>null</code> input String returns <code>null</code>.
3772    * A <code>null</code> separator splits on whitespace.</p>
3773    *
3774    * <pre>
3775    * StringUtils.splitByWholeSeparator(null, *, *)               = null
3776    * StringUtils.splitByWholeSeparator("", *, *)                 = []
3777    * StringUtils.splitByWholeSeparator("ab de fg", null, 0)      = ["ab", "de", "fg"]
3778    * StringUtils.splitByWholeSeparator("ab   de fg", null, 0)    = ["ab", "de", "fg"]
3779    * StringUtils.splitByWholeSeparator("ab:cd:ef", ":", 2)       = ["ab", "cd"]
3780    * StringUtils.splitByWholeSeparator("abstemiouslyaeiouyabstemiously", "aeiouy", 2) = ["bst", "m"]
3781    * StringUtils.splitByWholeSeparator("abstemiouslyaeiouyabstemiously", "aeiouy", 2)  = ["abstemiously", "abstemiously"]
3782    * </pre>
3783    *
3784    * @param str  the String to parse, may be null
3785    * @param separator  String containing the String to be used as a delimiter,
3786    *  <code>null</code> splits on whitespace
3787    * @param max  the maximum number of elements to include in the returned
3788    *  array. A zero or negative value implies no limit.
3789    * @return an array of parsed Strings, <code>null</code> if null String was input
3790    */
3791   @SuppressWarnings("unchecked")
3792   public static String[] splitByWholeSeparator(String str, String separator, int max) {
3793     if (str == null) {
3794       return null;
3795     }
3796 
3797     int len = str.length();
3798 
3799     if (len == 0) {
3800       return EMPTY_STRING_ARRAY;
3801     }
3802 
3803     if ((separator == null) || ("".equals(separator))) {
3804       // Split on whitespace.
3805       return split(str, null, max);
3806     }
3807 
3808     int separatorLength = separator.length();
3809 
3810     ArrayList substrings = new ArrayList();
3811     int numberOfSubstrings = 0;
3812     int beg = 0;
3813     int end = 0;
3814     while (end < len) {
3815       end = str.indexOf(separator, beg);
3816 
3817       if (end > -1) {
3818         if (end > beg) {
3819           numberOfSubstrings += 1;
3820 
3821           if (numberOfSubstrings == max) {
3822             end = len;
3823             substrings.add(str.substring(beg));
3824           } else {
3825             // The following is OK, because String.substring( beg, end ) excludes
3826             // the character at the position 'end'.
3827             substrings.add(str.substring(beg, end));
3828 
3829             // Set the starting point for the next search.
3830             // The following is equivalent to beg = end + (separatorLength - 1) + 1,
3831             // which is the right calculation:
3832             beg = end + separatorLength;
3833           }
3834         } else {
3835           // We found a consecutive occurrence of the separator, so skip it.
3836           beg = end + separatorLength;
3837         }
3838       } else {
3839         // String.substring( beg ) goes from 'beg' to the end of the String.
3840         substrings.add(str.substring(beg));
3841         end = len;
3842       }
3843     }
3844 
3845     return (String[]) substrings.toArray(new String[substrings.size()]);
3846   }
3847 
3848   //-----------------------------------------------------------------------
3849   /**
3850    * <p>Splits the provided text into an array, using whitespace as the
3851    * separator, preserving all tokens, including empty tokens created by 
3852    * adjacent separators. This is an alternative to using StringTokenizer.
3853    * Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
3854    *
3855    * <p>The separator is not included in the returned String array.
3856    * Adjacent separators are treated as separators for empty tokens.
3857    * For more control over the split use the StrTokenizer class.</p>
3858    *
3859    * <p>A <code>null</code> input String returns <code>null</code>.</p>
3860    *
3861    * <pre>
3862    * StringUtils.splitPreserveAllTokens(null)       = null
3863    * StringUtils.splitPreserveAllTokens("")         = []
3864    * StringUtils.splitPreserveAllTokens("abc def")  = ["abc", "def"]
3865    * StringUtils.splitPreserveAllTokens("abc  def") = ["abc", "", "def"]
3866    * StringUtils.splitPreserveAllTokens(" abc ")    = ["", "abc", ""]
3867    * </pre>
3868    *
3869    * @param str  the String to parse, may be <code>null</code>
3870    * @return an array of parsed Strings, <code>null</code> if null String input
3871    * @since 2.1
3872    */
3873   public static String[] splitPreserveAllTokens(String str) {
3874     return splitWorker(str, null, -1, true);
3875   }
3876 
3877   /**
3878    * <p>Splits the provided text into an array, separator specified,
3879    * preserving all tokens, including empty tokens created by adjacent
3880    * separators. This is an alternative to using StringTokenizer.</p>
3881    *
3882    * <p>The separator is not included in the returned String array.
3883    * Adjacent separators are treated as separators for empty tokens.
3884    * For more control over the split use the StrTokenizer class.</p>
3885    *
3886    * <p>A <code>null</code> input String returns <code>null</code>.</p>
3887    *
3888    * <pre>
3889    * StringUtils.splitPreserveAllTokens(null, *)         = null
3890    * StringUtils.splitPreserveAllTokens("", *)           = []
3891    * StringUtils.splitPreserveAllTokens("a.b.c", '.')    = ["a", "b", "c"]
3892    * StringUtils.splitPreserveAllTokens("a..b.c", '.')   = ["a", "b", "c"]
3893    * StringUtils.splitPreserveAllTokens("a:b:c", '.')    = ["a:b:c"]
3894    * StringUtils.splitPreserveAllTokens("a\tb\nc", null) = ["a", "b", "c"]
3895    * StringUtils.splitPreserveAllTokens("a b c", ' ')    = ["a", "b", "c"]
3896    * StringUtils.splitPreserveAllTokens("a b c ", ' ')   = ["a", "b", "c", ""]
3897    * StringUtils.splitPreserveAllTokens("a b c ", ' ')   = ["a", "b", "c", "", ""]
3898    * StringUtils.splitPreserveAllTokens(" a b c", ' ')   = ["", a", "b", "c"]
3899    * StringUtils.splitPreserveAllTokens("  a b c", ' ')  = ["", "", a", "b", "c"]
3900    * StringUtils.splitPreserveAllTokens(" a b c ", ' ')  = ["", a", "b", "c", ""]
3901    * </pre>
3902    *
3903    * @param str  the String to parse, may be <code>null</code>
3904    * @param separatorChar  the character used as the delimiter,
3905    *  <code>null</code> splits on whitespace
3906    * @return an array of parsed Strings, <code>null</code> if null String input
3907    * @since 2.1
3908    */
3909   public static String[] splitPreserveAllTokens(String str, char separatorChar) {
3910     return splitWorker(str, separatorChar, true);
3911   }
3912 
3913   /**
3914    * Performs the logic for the <code>split</code> and 
3915    * <code>splitPreserveAllTokens</code> methods that do not return a
3916    * maximum array length.
3917    *
3918    * @param str  the String to parse, may be <code>null</code>
3919    * @param separatorChar the separate character
3920    * @param preserveAllTokens if <code>true</code>, adjacent separators are
3921    * treated as empty token separators; if <code>false</code>, adjacent
3922    * separators are treated as one separator.
3923    * @return an array of parsed Strings, <code>null</code> if null String input
3924    */
3925   @SuppressWarnings("unchecked")
3926   private static String[] splitWorker(String str, char separatorChar,
3927       boolean preserveAllTokens) {
3928     // Performance tuned for 2.0 (JDK1.4)
3929 
3930     if (str == null) {
3931       return null;
3932     }
3933     int len = str.length();
3934     if (len == 0) {
3935       return EMPTY_STRING_ARRAY;
3936     }
3937     List list = new ArrayList();
3938     int i = 0, start = 0;
3939     boolean match = false;
3940     boolean lastMatch = false;
3941     while (i < len) {
3942       if (str.charAt(i) == separatorChar) {
3943         if (match || preserveAllTokens) {
3944           list.add(str.substring(start, i));
3945           match = false;
3946           lastMatch = true;
3947         }
3948         start = ++i;
3949         continue;
3950       }
3951       lastMatch = false;
3952       match = true;
3953       i++;
3954     }
3955     if (match || (preserveAllTokens && lastMatch)) {
3956       list.add(str.substring(start, i));
3957     }
3958     return (String[]) list.toArray(new String[list.size()]);
3959   }
3960 
3961   /**
3962    * <p>Splits the provided text into an array, separators specified, 
3963    * preserving all tokens, including empty tokens created by adjacent
3964    * separators. This is an alternative to using StringTokenizer.</p>
3965    *
3966    * <p>The separator is not included in the returned String array.
3967    * Adjacent separators are treated as separators for empty tokens.
3968    * For more control over the split use the StrTokenizer class.</p>
3969    *
3970    * <p>A <code>null</code> input String returns <code>null</code>.
3971    * A <code>null</code> separatorChars splits on whitespace.</p>
3972    *
3973    * <pre>
3974    * StringUtils.splitPreserveAllTokens(null, *)           = null
3975    * StringUtils.splitPreserveAllTokens("", *)             = []
3976    * StringUtils.splitPreserveAllTokens("abc def", null)   = ["abc", "def"]
3977    * StringUtils.splitPreserveAllTokens("abc def", " ")    = ["abc", "def"]
3978    * StringUtils.splitPreserveAllTokens("abc  def", " ")   = ["abc", "", def"]
3979    * StringUtils.splitPreserveAllTokens("ab:cd:ef", ":")   = ["ab", "cd", "ef"]
3980    * StringUtils.splitPreserveAllTokens("ab:cd:ef:", ":")  = ["ab", "cd", "ef", ""]
3981    * StringUtils.splitPreserveAllTokens("ab:cd:ef::", ":") = ["ab", "cd", "ef", "", ""]
3982    * StringUtils.splitPreserveAllTokens("ab::cd:ef", ":")  = ["ab", "", cd", "ef"]
3983    * StringUtils.splitPreserveAllTokens(":cd:ef", ":")     = ["", cd", "ef"]
3984    * StringUtils.splitPreserveAllTokens("::cd:ef", ":")    = ["", "", cd", "ef"]
3985    * StringUtils.splitPreserveAllTokens(":cd:ef:", ":")    = ["", cd", "ef", ""]
3986    * </pre>
3987    *
3988    * @param str  the String to parse, may be <code>null</code>
3989    * @param separatorChars  the characters used as the delimiters,
3990    *  <code>null</code> splits on whitespace
3991    * @return an array of parsed Strings, <code>null</code> if null String input
3992    * @since 2.1
3993    */
3994   public static String[] splitPreserveAllTokens(String str, String separatorChars) {
3995     return splitWorker(str, separatorChars, -1, true);
3996   }
3997 
3998   /**
3999    * <p>Splits the provided text into an array with a maximum length,
4000    * separators specified, preserving all tokens, including empty tokens 
4001    * created by adjacent separators.</p>
4002    *
4003    * <p>The separator is not included in the returned String array.
4004    * Adjacent separators are treated as separators for empty tokens.
4005    * Adjacent separators are treated as one separator.</p>
4006    *
4007    * <p>A <code>null</code> input String returns <code>null</code>.
4008    * A <code>null</code> separatorChars splits on whitespace.</p>
4009    *
4010    * <p>If more than <code>max</code> delimited substrings are found, the last
4011    * returned string includes all characters after the first <code>max - 1</code>
4012    * returned strings (including separator characters).</p>
4013    *
4014    * <pre>
4015    * StringUtils.splitPreserveAllTokens(null, *, *)            = null
4016    * StringUtils.splitPreserveAllTokens("", *, *)              = []
4017    * StringUtils.splitPreserveAllTokens("ab de fg", null, 0)   = ["ab", "cd", "ef"]
4018    * StringUtils.splitPreserveAllTokens("ab   de fg", null, 0) = ["ab", "cd", "ef"]
4019    * StringUtils.splitPreserveAllTokens("ab:cd:ef", ":", 0)    = ["ab", "cd", "ef"]
4020    * StringUtils.splitPreserveAllTokens("ab:cd:ef", ":", 2)    = ["ab", "cd:ef"]
4021    * StringUtils.splitPreserveAllTokens("ab   de fg", null, 2) = ["ab", "  de fg"]
4022    * StringUtils.splitPreserveAllTokens("ab   de fg", null, 3) = ["ab", "", " de fg"]
4023    * StringUtils.splitPreserveAllTokens("ab   de fg", null, 4) = ["ab", "", "", "de fg"]
4024    * </pre>
4025    *
4026    * @param str  the String to parse, may be <code>null</code>
4027    * @param separatorChars  the characters used as the delimiters,
4028    *  <code>null</code> splits on whitespace
4029    * @param max  the maximum number of elements to include in the
4030    *  array. A zero or negative value implies no limit
4031    * @return an array of parsed Strings, <code>null</code> if null String input
4032    * @since 2.1
4033    */
4034   public static String[] splitPreserveAllTokens(String str, String separatorChars, int max) {
4035     return splitWorker(str, separatorChars, max, true);
4036   }
4037 
4038   /**
4039    * Performs the logic for the <code>split</code> and 
4040    * <code>splitPreserveAllTokens</code> methods that return a maximum array 
4041    * length.
4042    *
4043    * @param str  the String to parse, may be <code>null</code>
4044    * @param separatorChars the separate character
4045    * @param max  the maximum number of elements to include in the
4046    *  array. A zero or negative value implies no limit.
4047    * @param preserveAllTokens if <code>true</code>, adjacent separators are
4048    * treated as empty token separators; if <code>false</code>, adjacent
4049    * separators are treated as one separator.
4050    * @return an array of parsed Strings, <code>null</code> if null String input
4051    */
4052   @SuppressWarnings("unchecked")
4053   private static String[] splitWorker(String str, String separatorChars, int max,
4054       boolean preserveAllTokens) {
4055     // Performance tuned for 2.0 (JDK1.4)
4056     // Direct code is quicker than StringTokenizer.
4057     // Also, StringTokenizer uses isSpace() not isWhitespace()
4058 
4059     if (str == null) {
4060       return null;
4061     }
4062     int len = str.length();
4063     if (len == 0) {
4064       return EMPTY_STRING_ARRAY;
4065     }
4066     List list = new ArrayList();
4067     int sizePlus1 = 1;
4068     int i = 0, start = 0;
4069     boolean match = false;
4070     boolean lastMatch = false;
4071     if (separatorChars == null) {
4072       // Null separator means use whitespace
4073       while (i < len) {
4074         if (Character.isWhitespace(str.charAt(i))) {
4075           if (match || preserveAllTokens) {
4076             lastMatch = true;
4077             if (sizePlus1++ == max) {
4078               i = len;
4079               lastMatch = false;
4080             }
4081             list.add(str.substring(start, i));
4082             match = false;
4083           }
4084           start = ++i;
4085           continue;
4086         }
4087         lastMatch = false;
4088         match = true;
4089         i++;
4090       }
4091     } else if (separatorChars.length() == 1) {
4092       // Optimise 1 character case
4093       char sep = separatorChars.charAt(0);
4094       while (i < len) {
4095         if (str.charAt(i) == sep) {
4096           if (match || preserveAllTokens) {
4097             lastMatch = true;
4098             if (sizePlus1++ == max) {
4099               i = len;
4100               lastMatch = false;
4101             }
4102             list.add(str.substring(start, i));
4103             match = false;
4104           }
4105           start = ++i;
4106           continue;
4107         }
4108         lastMatch = false;
4109         match = true;
4110         i++;
4111       }
4112     } else {
4113       // standard case
4114       while (i < len) {
4115         if (separatorChars.indexOf(str.charAt(i)) >= 0) {
4116           if (match || preserveAllTokens) {
4117             lastMatch = true;
4118             if (sizePlus1++ == max) {
4119               i = len;
4120               lastMatch = false;
4121             }
4122             list.add(str.substring(start, i));
4123             match = false;
4124           }
4125           start = ++i;
4126           continue;
4127         }
4128         lastMatch = false;
4129         match = true;
4130         i++;
4131       }
4132     }
4133     if (match || (preserveAllTokens && lastMatch)) {
4134       list.add(str.substring(start, i));
4135     }
4136     return (String[]) list.toArray(new String[list.size()]);
4137   }
4138 
4139   /**
4140    * <p>Joins the elements of the provided array into a single String
4141    * containing the provided list of elements.</p>
4142    *
4143    * <p>No separator is added to the joined String.
4144    * Null objects or empty strings within the array are represented by
4145    * empty strings.</p>
4146    *
4147    * <pre>
4148    * StringUtils.join(null)            = null
4149    * StringUtils.join([])              = ""
4150    * StringUtils.join([null])          = ""
4151    * StringUtils.join(["a", "b", "c"]) = "abc"
4152    * StringUtils.join([null, "", "a"]) = "a"
4153    * </pre>
4154    *
4155    * @param array  the array of values to join together, may be null
4156    * @return the joined String, <code>null</code> if null array input
4157    * @since 2.0
4158    */
4159   public static String join(Object[] array) {
4160     return join(array, null);
4161   }
4162 
4163   /**
4164    * <p>Joins the elements of the provided array into a single String
4165    * containing the provided list of elements.</p>
4166    *
4167    * <p>No delimiter is added before or after the list.
4168    * Null objects or empty strings within the array are represented by
4169    * empty strings.</p>
4170    *
4171    * <pre>
4172    * StringUtils.join(null, *)               = null
4173    * StringUtils.join([], *)                 = ""
4174    * StringUtils.join([null], *)             = ""
4175    * StringUtils.join(["a", "b", "c"], ';')  = "a;b;c"
4176    * StringUtils.join(["a", "b", "c"], null) = "abc"
4177    * StringUtils.join([null, "", "a"], ';')  = ";;a"
4178    * </pre>
4179    *
4180    * @param array  the array of values to join together, may be null
4181    * @param separator  the separator character to use
4182    * @return the joined String, <code>null</code> if null array input
4183    * @since 2.0
4184    */
4185   public static String join(Object[] array, char separator) {
4186     if (array == null) {
4187       return null;
4188     }
4189     int arraySize = array.length;
4190     int bufSize = (arraySize == 0 ? 0 : ((array[0] == null ? 16 : array[0].toString()
4191         .length()) + 1)
4192         * arraySize);
4193     StringBuffer buf = new StringBuffer(bufSize);
4194 
4195     for (int i = 0; i < arraySize; i++) {
4196       if (i > 0) {
4197         buf.append(separator);
4198       }
4199       if (array[i] != null) {
4200         buf.append(array[i]);
4201       }
4202     }
4203     return buf.toString();
4204   }
4205 
4206   /**
4207    * <p>Joins the elements of the provided array into a single String
4208    * containing the provided list of elements.</p>
4209    *
4210    * <p>No delimiter is added before or after the list.
4211    * A <code>null</code> separator is the same as an empty String ("").
4212    * Null objects or empty strings within the array are represented by
4213    * empty strings.</p>
4214    *
4215    * <pre>
4216    * StringUtils.join(null, *)                = null
4217    * StringUtils.join([], *)                  = ""
4218    * StringUtils.join([null], *)              = ""
4219    * StringUtils.join(["a", "b", "c"], "--")  = "a--b--c"
4220    * StringUtils.join(["a", "b", "c"], null)  = "abc"
4221    * StringUtils.join(["a", "b", "c"], "")    = "abc"
4222    * StringUtils.join([null, "", "a"], ',')   = ",,a"
4223    * </pre>
4224    *
4225    * @param array  the array of values to join together, may be null
4226    * @param separator  the separator character to use, null treated as ""
4227    * @return the joined String, <code>null</code> if null array input
4228    */
4229   public static String join(Object[] array, String separator) {
4230     if (array == null) {
4231       return null;
4232     }
4233     if (separator == null) {
4234       separator = "";
4235     }
4236     int arraySize = array.length;
4237 
4238     // ArraySize ==  0: Len = 0
4239     // ArraySize > 0:   Len = NofStrings *(len(firstString) + len(separator))
4240     //           (Assuming that all Strings are roughly equally long)
4241     int bufSize = ((arraySize == 0) ? 0 : arraySize
4242         * ((array[0] == null ? 16 : array[0].toString().length()) + separator.length()));
4243 
4244     StringBuffer buf = new StringBuffer(bufSize);
4245 
4246     for (int i = 0; i < arraySize; i++) {
4247       if (i > 0) {
4248         buf.append(separator);
4249       }
4250       if (array[i] != null) {
4251         buf.append(array[i]);
4252       }
4253     }
4254     return buf.toString();
4255   }
4256 
4257   /**
4258    * <p>Joins the elements of the provided <code>Iterator</code> into
4259    * a single String containing the provided elements.</p>
4260    *
4261    * <p>No delimiter is added before or after the list. Null objects or empty
4262    * strings within the iteration are represented by empty strings.</p>
4263    *
4264    * <p>See the examples here: {@link #join(Object[],char)}. </p>
4265    *
4266    * @param iterator  the <code>Iterator</code> of values to join together, may be null
4267    * @param separator  the separator character to use
4268    * @return the joined String, <code>null</code> if null iterator input
4269    * @since 2.0
4270    */
4271   public static String join(Iterator iterator, char separator) {
4272     if (iterator == null) {
4273       return null;
4274     }
4275     StringBuffer buf = new StringBuffer(256); // Java default is 16, probably too small
4276     while (iterator.hasNext()) {
4277       Object obj = iterator.next();
4278       if (obj != null) {
4279         buf.append(obj);
4280       }
4281       if (iterator.hasNext()) {
4282         buf.append(separator);
4283       }
4284     }
4285     return buf.toString();
4286   }
4287 
4288   /**
4289    * <p>Joins the elements of the provided <code>Iterator</code> into
4290    * a single String containing the provided elements.</p>
4291    *
4292    * <p>No delimiter is added before or after the list.
4293    * A <code>null</code> separator is the same as an empty String ("").</p>
4294    *
4295    * <p>See the examples here: {@link #join(Object[],String)}. </p>
4296    *
4297    * @param iterator  the <code>Iterator</code> of values to join together, may be null
4298    * @param separator  the separator character to use, null treated as ""
4299    * @return the joined String, <code>null</code> if null iterator input
4300    */
4301   public static String join(Iterator iterator, String separator) {
4302     if (iterator == null) {
4303       return null;
4304     }
4305     StringBuffer buf = new StringBuffer(256); // Java default is 16, probably too small
4306     while (iterator.hasNext()) {
4307       Object obj = iterator.next();
4308       if (obj != null) {
4309         buf.append(obj);
4310       }
4311       if ((separator != null) && iterator.hasNext()) {
4312         buf.append(separator);
4313       }
4314     }
4315     return buf.toString();
4316   }
4317 
4318   /**
4319    * <p>Returns either the passed in String,
4320    * or if the String is <code>null</code>, an empty String ("").</p>
4321    *
4322    * <pre>
4323    * StringUtils.defaultString(null)  = ""
4324    * StringUtils.defaultString("")    = ""
4325    * StringUtils.defaultString("bat") = "bat"
4326    * </pre>
4327    *
4328    * @see String#valueOf(Object)
4329    * @param str  the String to check, may be null
4330    * @return the passed in String, or the empty String if it
4331    *  was <code>null</code>
4332    */
4333   public static String defaultString(String str) {
4334     return str == null ? "" : str;
4335   }
4336 
4337   /**
4338    * <p>Returns either the passed in String, or if the String is
4339    * <code>null</code>, the value of <code>defaultStr</code>.</p>
4340    *
4341    * <pre>
4342    * StringUtils.defaultString(null, "NULL")  = "NULL"
4343    * StringUtils.defaultString("", "NULL")    = ""
4344    * StringUtils.defaultString("bat", "NULL") = "bat"
4345    * </pre>
4346    *
4347    * @see String#valueOf(Object)
4348    * @param str  the String to check, may be null
4349    * @param defaultStr  the default String to return
4350    *  if the input is <code>null</code>, may be null
4351    * @return the passed in String, or the default if it was <code>null</code>
4352    */
4353   public static String defaultString(String str, String defaultStr) {
4354     return str == null ? defaultStr : str;
4355   }
4356 
4357   /**
4358    * <p>Returns either the passed in String, or if the String is
4359    * empty or <code>null</code>, the value of <code>defaultStr</code>.</p>
4360    *
4361    * <pre>
4362    * StringUtils.defaultIfEmpty(null, "NULL")  = "NULL"
4363    * StringUtils.defaultIfEmpty("", "NULL")    = "NULL"
4364    * StringUtils.defaultIfEmpty("bat", "NULL") = "bat"
4365    * </pre>
4366    *
4367    * @param str  the String to check, may be null
4368    * @param defaultStr  the default String to return
4369    *  if the input is empty ("") or <code>null</code>, may be null
4370    * @return the passed in String, or the default
4371    */
4372   public static String defaultIfEmpty(String str, String defaultStr) {
4373     return isEmpty(str) ? defaultStr : str;
4374   }
4375 
4376   /**
4377    * <p>Capitalizes a String changing the first letter to title case as
4378    * per {@link Character#toTitleCase(char)}. No other letters are changed.</p>
4379    *
4380    * A <code>null</code> input String returns <code>null</code>.</p>
4381    *
4382    * <pre>
4383    * StringUtils.capitalize(null)  = null
4384    * StringUtils.capitalize("")    = ""
4385    * StringUtils.capitalize("cat") = "Cat"
4386    * StringUtils.capitalize("cAt") = "CAt"
4387    * </pre>
4388    *
4389    * @param str  the String to capitalize, may be null
4390    * @return the capitalized String, <code>null</code> if null String input
4391    * @since 2.0
4392    */
4393   public static String capitalize(String str) {
4394     int strLen;
4395     if (str == null || (strLen = str.length()) == 0) {
4396       return str;
4397     }
4398     return new StringBuffer(strLen).append(Character.toTitleCase(str.charAt(0))).append(
4399         str.substring(1)).toString();
4400   }
4401 
4402   /**
4403    * <p>Checks if String contains a search character, handling <code>null</code>.
4404    * This method uses {@link String#indexOf(int)}.</p>
4405    *
4406    * <p>A <code>null</code> or empty ("") String will return <code>false</code>.</p>
4407    *
4408    * <pre>
4409    * StringUtils.contains(null, *)    = false
4410    * StringUtils.contains("", *)      = false
4411    * StringUtils.contains("abc", 'a') = true
4412    * StringUtils.contains("abc", 'z') = false
4413    * </pre>
4414    *
4415    * @param str  the String to check, may be null
4416    * @param searchChar  the character to find
4417    * @return true if the String contains the search character,
4418    *  false if not or <code>null</code> string input
4419    * @since 2.0
4420    */
4421   public static boolean contains(String str, char searchChar) {
4422     if (isEmpty(str)) {
4423       return false;
4424     }
4425     return str.indexOf(searchChar) >= 0;
4426   }
4427 
4428   /**
4429    * <p>Checks if String contains a search String, handling <code>null</code>.
4430    * This method uses {@link String#indexOf(int)}.</p>
4431    *
4432    * <p>A <code>null</code> String will return <code>false</code>.</p>
4433    *
4434    * <pre>
4435    * StringUtils.contains(null, *)     = false
4436    * StringUtils.contains(*, null)     = false
4437    * StringUtils.contains("", "")      = true
4438    * StringUtils.contains("abc", "")   = true
4439    * StringUtils.contains("abc", "a")  = true
4440    * StringUtils.contains("abc", "z")  = false
4441    * </pre>
4442    *
4443    * @param str  the String to check, may be null
4444    * @param searchStr  the String to find, may be null
4445    * @return true if the String contains the search String,
4446    *  false if not or <code>null</code> string input
4447    * @since 2.0
4448    */
4449   public static boolean contains(String str, String searchStr) {
4450     if (str == null || searchStr == null) {
4451       return false;
4452     }
4453     return str.indexOf(searchStr) >= 0;
4454   }
4455 
4456   /**
4457    * An empty immutable <code>String</code> array.
4458    */
4459   public static final String[] EMPTY_STRING_ARRAY = new String[0];
4460 
4461   /**
4462    * <p>Compares two objects for equality, where either one or both
4463    * objects may be <code>null</code>.</p>
4464    *
4465    * <pre>
4466    * ObjectUtils.equals(null, null)                  = true
4467    * ObjectUtils.equals(null, "")                    = false
4468    * ObjectUtils.equals("", null)                    = false
4469    * ObjectUtils.equals("", "")                      = true
4470    * ObjectUtils.equals(Boolean.TRUE, null)          = false
4471    * ObjectUtils.equals(Boolean.TRUE, "true")        = false
4472    * ObjectUtils.equals(Boolean.TRUE, Boolean.TRUE)  = true
4473    * ObjectUtils.equals(Boolean.TRUE, Boolean.FALSE) = false
4474    * </pre>
4475    *
4476    * @param object1  the first object, may be <code>null</code>
4477    * @param object2  the second object, may be <code>null</code>
4478    * @return <code>true</code> if the values of both objects are the same
4479    */
4480   public static boolean equals(Object object1, Object object2) {
4481     if (object1 == object2) {
4482       return true;
4483     }
4484     if ((object1 == null) || (object2 == null)) {
4485       return false;
4486     }
4487     return object1.equals(object2);
4488   }
4489 
4490   /**
4491    * An empty immutable <code>Object</code> array.
4492    */
4493   public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
4494 
4495   /**
4496    * strip the last slash (/ or \) from a string if it exists
4497    * 
4498    * @param input
4499    * 
4500    * @return input - the last / or \
4501    */
4502   public static String stripLastSlashIfExists(String input) {
4503     if ((input == null) || (input.length() == 0)) {
4504       return null;
4505     }
4506 
4507     char lastChar = input.charAt(input.length() - 1);
4508 
4509     if ((lastChar == '\\') || (lastChar == '/')) {
4510       return input.substring(0, input.length() - 1);
4511     }
4512 
4513     return input;
4514   }
4515 
4516   /**
4517    * <p>Strips any of a set of characters from the start of a String.</p>
4518    *
4519    * <p>A <code>null</code> input String returns <code>null</code>.
4520    * An empty string ("") input returns the empty string.</p>
4521    *
4522    * <p>If the stripChars String is <code>null</code>, whitespace is
4523    * stripped as defined by {@link Character#isWhitespace(char)}.</p>
4524    *
4525    * <pre>
4526    * StringUtils.stripStart(null, *)          = null
4527    * StringUtils.stripStart("", *)            = ""
4528    * StringUtils.stripStart("abc", "")        = "abc"
4529    * StringUtils.stripStart("abc", null)      = "abc"
4530    * StringUtils.stripStart("  abc", null)    = "abc"
4531    * StringUtils.stripStart("abc  ", null)    = "abc  "
4532    * StringUtils.stripStart(" abc ", null)    = "abc "
4533    * StringUtils.stripStart("yxabc  ", "xyz") = "abc  "
4534    * </pre>
4535    *
4536    * @param str  the String to remove characters from, may be null
4537    * @param stripChars  the characters to remove, null treated as whitespace
4538    * @return the stripped String, <code>null</code> if null String input
4539    */
4540   public static String stripStart(String str, String stripChars) {
4541     int strLen;
4542     if (str == null || (strLen = str.length()) == 0) {
4543       return str;
4544     }
4545     int start = 0;
4546     if (stripChars == null) {
4547       while ((start != strLen) && Character.isWhitespace(str.charAt(start))) {
4548         start++;
4549       }
4550     } else if (stripChars.length() == 0) {
4551       return str;
4552     } else {
4553       while ((start != strLen) && (stripChars.indexOf(str.charAt(start)) != -1)) {
4554         start++;
4555       }
4556     }
4557     return str.substring(start);
4558   }
4559 
4560   /**
4561    * <p>Strips any of a set of characters from the end of a String.</p>
4562    *
4563    * <p>A <code>null</code> input String returns <code>null</code>.
4564    * An empty string ("") input returns the empty string.</p>
4565    *
4566    * <p>If the stripChars String is <code>null</code>, whitespace is
4567    * stripped as defined by {@link Character#isWhitespace(char)}.</p>
4568    *
4569    * <pre>
4570    * StringUtils.stripEnd(null, *)          = null
4571    * StringUtils.stripEnd("", *)            = ""
4572    * StringUtils.stripEnd("abc", "")        = "abc"
4573    * StringUtils.stripEnd("abc", null)      = "abc"
4574    * StringUtils.stripEnd("  abc", null)    = "  abc"
4575    * StringUtils.stripEnd("abc  ", null)    = "abc"
4576    * StringUtils.stripEnd(" abc ", null)    = " abc"
4577    * StringUtils.stripEnd("  abcyx", "xyz") = "  abc"
4578    * </pre>
4579    *
4580    * @param str  the String to remove characters from, may be null
4581    * @param stripChars  the characters to remove, null treated as whitespace
4582    * @return the stripped String, <code>null</code> if null String input
4583    */
4584   public static String stripEnd(String str, String stripChars) {
4585     int end;
4586     if (str == null || (end = str.length()) == 0) {
4587       return str;
4588     }
4589 
4590     if (stripChars == null) {
4591       while ((end != 0) && Character.isWhitespace(str.charAt(end - 1))) {
4592         end--;
4593       }
4594     } else if (stripChars.length() == 0) {
4595       return str;
4596     } else {
4597       while ((end != 0) && (stripChars.indexOf(str.charAt(end - 1)) != -1)) {
4598         end--;
4599       }
4600     }
4601     return str.substring(0, end);
4602   }
4603 
4604   /**
4605    * The empty String <code>""</code>.
4606    * @since 2.0
4607    */
4608   public static final String EMPTY = "";
4609 
4610   /**
4611    * Represents a failed index search.
4612    * @since 2.1
4613    */
4614   public static final int INDEX_NOT_FOUND = -1;
4615 
4616   /**
4617    * <p>The maximum size to which the padding constant(s) can expand.</p>
4618    */
4619   private static final int PAD_LIMIT = 8192;
4620 
4621   /**
4622    * <p>An array of <code>String</code>s used for padding.</p>
4623    *
4624    * <p>Used for efficient space padding. The length of each String expands as needed.</p>
4625    */
4626   private static final String[] PADDING = new String[Character.MAX_VALUE];
4627 
4628   static {
4629     // space padding is most common, start with 64 chars
4630     PADDING[32] = "                                                                ";
4631   }
4632 
4633   /**
4634    * <p>Repeat a String <code>repeat</code> times to form a
4635    * new String.</p>
4636    *
4637    * <pre>
4638    * StringUtils.repeat(null, 2) = null
4639    * StringUtils.repeat("", 0)   = ""
4640    * StringUtils.repeat("", 2)   = ""
4641    * StringUtils.repeat("a", 3)  = "aaa"
4642    * StringUtils.repeat("ab", 2) = "abab"
4643    * StringUtils.repeat("a", -2) = ""
4644    * </pre>
4645    *
4646    * @param str  the String to repeat, may be null
4647    * @param repeat  number of times to repeat str, negative treated as zero
4648    * @return a new String consisting of the original String repeated,
4649    *  <code>null</code> if null String input
4650    */
4651   public static String repeat(String str, int repeat) {
4652     // Performance tuned for 2.0 (JDK1.4)
4653 
4654     if (str == null) {
4655       return null;
4656     }
4657     if (repeat <= 0) {
4658       return EMPTY;
4659     }
4660     int inputLength = str.length();
4661     if (repeat == 1 || inputLength == 0) {
4662       return str;
4663     }
4664     if (inputLength == 1 && repeat <= PAD_LIMIT) {
4665       return padding(repeat, str.charAt(0));
4666     }
4667 
4668     int outputLength = inputLength * repeat;
4669     switch (inputLength) {
4670       case 1:
4671         char ch = str.charAt(0);
4672         char[] output1 = new char[outputLength];
4673         for (int i = repeat - 1; i >= 0; i--) {
4674           output1[i] = ch;
4675         }
4676         return new String(output1);
4677       case 2:
4678         char ch0 = str.charAt(0);
4679         char ch1 = str.charAt(1);
4680         char[] output2 = new char[outputLength];
4681         for (int i = repeat * 2 - 2; i >= 0; i--, i--) {
4682           output2[i] = ch0;
4683           output2[i + 1] = ch1;
4684         }
4685         return new String(output2);
4686       default:
4687         StringBuffer buf = new StringBuffer(outputLength);
4688         for (int i = 0; i < repeat; i++) {
4689           buf.append(str);
4690         }
4691         return buf.toString();
4692     }
4693   }
4694 
4695   /**
4696    * <p>Returns padding using the specified delimiter repeated
4697    * to a given length.</p>
4698    *
4699    * <pre>
4700    * StringUtils.padding(0, 'e')  = ""
4701    * StringUtils.padding(3, 'e')  = "eee"
4702    * StringUtils.padding(-2, 'e') = IndexOutOfBoundsException
4703    * </pre>
4704    *
4705    * @param repeat  number of times to repeat delim
4706    * @param padChar  character to repeat
4707    * @return String with repeated character
4708    * @throws IndexOutOfBoundsException if <code>repeat &lt; 0</code>
4709    */
4710   private static String padding(int repeat, char padChar) {
4711     // be careful of synchronization in this method
4712     // we are assuming that get and set from an array index is atomic
4713     String pad = PADDING[padChar];
4714     if (pad == null) {
4715       pad = String.valueOf(padChar);
4716     }
4717     while (pad.length() < repeat) {
4718       pad = pad.concat(pad);
4719     }
4720     PADDING[padChar] = pad;
4721     return pad.substring(0, repeat);
4722   }
4723 
4724   /**
4725    * <p>Right pad a String with spaces (' ').</p>
4726    *
4727    * <p>The String is padded to the size of <code>size</code>.</p>
4728    *
4729    * <pre>
4730    * StringUtils.rightPad(null, *)   = null
4731    * StringUtils.rightPad("", 3)     = "   "
4732    * StringUtils.rightPad("bat", 3)  = "bat"
4733    * StringUtils.rightPad("bat", 5)  = "bat  "
4734    * StringUtils.rightPad("bat", 1)  = "bat"
4735    * StringUtils.rightPad("bat", -1) = "bat"
4736    * </pre>
4737    *
4738    * @param str  the String to pad out, may be null
4739    * @param size  the size to pad to
4740    * @return right padded String or original String if no padding is necessary,
4741    *  <code>null</code> if null String input
4742    */
4743   public static String rightPad(String str, int size) {
4744     return rightPad(str, size, ' ');
4745   }
4746 
4747   /**
4748    * <p>Right pad a String with a specified character.</p>
4749    *
4750    * <p>The String is padded to the size of <code>size</code>.</p>
4751    *
4752    * <pre>
4753    * StringUtils.rightPad(null, *, *)     = null
4754    * StringUtils.rightPad("", 3, 'z')     = "zzz"
4755    * StringUtils.rightPad("bat", 3, 'z')  = "bat"
4756    * StringUtils.rightPad("bat", 5, 'z')  = "batzz"
4757    * StringUtils.rightPad("bat", 1, 'z')  = "bat"
4758    * StringUtils.rightPad("bat", -1, 'z') = "bat"
4759    * </pre>
4760    *
4761    * @param str  the String to pad out, may be null
4762    * @param size  the size to pad to
4763    * @param padChar  the character to pad with
4764    * @return right padded String or original String if no padding is necessary,
4765    *  <code>null</code> if null String input
4766    * @since 2.0
4767    */
4768   public static String rightPad(String str, int size, char padChar) {
4769     if (str == null) {
4770       return null;
4771     }
4772     int pads = size - str.length();
4773     if (pads <= 0) {
4774       return str; // returns original String when possible
4775     }
4776     if (pads > PAD_LIMIT) {
4777       return rightPad(str, size, String.valueOf(padChar));
4778     }
4779     return str.concat(padding(pads, padChar));
4780   }
4781 
4782   /**
4783    * <p>Right pad a String with a specified String.</p>
4784    *
4785    * <p>The String is padded to the size of <code>size</code>.</p>
4786    *
4787    * <pre>
4788    * StringUtils.rightPad(null, *, *)      = null
4789    * StringUtils.rightPad("", 3, "z")      = "zzz"
4790    * StringUtils.rightPad("bat", 3, "yz")  = "bat"
4791    * StringUtils.rightPad("bat", 5, "yz")  = "batyz"
4792    * StringUtils.rightPad("bat", 8, "yz")  = "batyzyzy"
4793    * StringUtils.rightPad("bat", 1, "yz")  = "bat"
4794    * StringUtils.rightPad("bat", -1, "yz") = "bat"
4795    * StringUtils.rightPad("bat", 5, null)  = "bat  "
4796    * StringUtils.rightPad("bat", 5, "")    = "bat  "
4797    * </pre>
4798    *
4799    * @param str  the String to pad out, may be null
4800    * @param size  the size to pad to
4801    * @param padStr  the String to pad with, null or empty treated as single space
4802    * @return right padded String or original String if no padding is necessary,
4803    *  <code>null</code> if null String input
4804    */
4805   public static String rightPad(String str, int size, String padStr) {
4806     if (str == null) {
4807       return null;
4808     }
4809     if (isEmpty(padStr)) {
4810       padStr = " ";
4811     }
4812     int padLen = padStr.length();
4813     int strLen = str.length();
4814     int pads = size - strLen;
4815     if (pads <= 0) {
4816       return str; // returns original String when possible
4817     }
4818     if (padLen == 1 && pads <= PAD_LIMIT) {
4819       return rightPad(str, size, padStr.charAt(0));
4820     }
4821 
4822     if (pads == padLen) {
4823       return str.concat(padStr);
4824     } else if (pads < padLen) {
4825       return str.concat(padStr.substring(0, pads));
4826     } else {
4827       char[] padding = new char[pads];
4828       char[] padChars = padStr.toCharArray();
4829       for (int i = 0; i < pads; i++) {
4830         padding[i] = padChars[i % padLen];
4831       }
4832       return str.concat(new String(padding));
4833     }
4834   }
4835 
4836   /**
4837    * <p>Left pad a String with spaces (' ').</p>
4838    *
4839    * <p>The String is padded to the size of <code>size<code>.</p>
4840    *
4841    * <pre>
4842    * StringUtils.leftPad(null, *)   = null
4843    * StringUtils.leftPad("", 3)     = "   "
4844    * StringUtils.leftPad("bat", 3)  = "bat"
4845    * StringUtils.leftPad("bat", 5)  = "  bat"
4846    * StringUtils.leftPad("bat", 1)  = "bat"
4847    * StringUtils.leftPad("bat", -1) = "bat"
4848    * </pre>
4849    *
4850    * @param str  the String to pad out, may be null
4851    * @param size  the size to pad to
4852    * @return left padded String or original String if no padding is necessary,
4853    *  <code>null</code> if null String input
4854    */
4855   public static String leftPad(String str, int size) {
4856     return leftPad(str, size, ' ');
4857   }
4858 
4859   /**
4860    * <p>Left pad a String with a specified character.</p>
4861    *
4862    * <p>Pad to a size of <code>size</code>.</p>
4863    *
4864    * <pre>
4865    * StringUtils.leftPad(null, *, *)     = null
4866    * StringUtils.leftPad("", 3, 'z')     = "zzz"
4867    * StringUtils.leftPad("bat", 3, 'z')  = "bat"
4868    * StringUtils.leftPad("bat", 5, 'z')  = "zzbat"
4869    * StringUtils.leftPad("bat", 1, 'z')  = "bat"
4870    * StringUtils.leftPad("bat", -1, 'z') = "bat"
4871    * </pre>
4872    *
4873    * @param str  the String to pad out, may be null
4874    * @param size  the size to pad to
4875    * @param padChar  the character to pad with
4876    * @return left padded String or original String if no padding is necessary,
4877    *  <code>null</code> if null String input
4878    * @since 2.0
4879    */
4880   public static String leftPad(String str, int size, char padChar) {
4881     if (str == null) {
4882       return null;
4883     }
4884     int pads = size - str.length();
4885     if (pads <= 0) {
4886       return str; // returns original String when possible
4887     }
4888     if (pads > PAD_LIMIT) {
4889       return leftPad(str, size, String.valueOf(padChar));
4890     }
4891     return padding(pads, padChar).concat(str);
4892   }
4893 
4894   /**
4895    * <p>Left pad a String with a specified String.</p>
4896    *
4897    * <p>Pad to a size of <code>size</code>.</p>
4898    *
4899    * <pre>
4900    * StringUtils.leftPad(null, *, *)      = null
4901    * StringUtils.leftPad("", 3, "z")      = "zzz"
4902    * StringUtils.leftPad("bat", 3, "yz")  = "bat"
4903    * StringUtils.leftPad("bat", 5, "yz")  = "yzbat"
4904    * StringUtils.leftPad("bat", 8, "yz")  = "yzyzybat"
4905    * StringUtils.leftPad("bat", 1, "yz")  = "bat"
4906    * StringUtils.leftPad("bat", -1, "yz") = "bat"
4907    * StringUtils.leftPad("bat", 5, null)  = "  bat"
4908    * StringUtils.leftPad("bat", 5, "")    = "  bat"
4909    * </pre>
4910    *
4911    * @param str  the String to pad out, may be null
4912    * @param size  the size to pad to
4913    * @param padStr  the String to pad with, null or empty treated as single space
4914    * @return left padded String or original String if no padding is necessary,
4915    *  <code>null</code> if null String input
4916    */
4917   public static String leftPad(String str, int size, String padStr) {
4918     if (str == null) {
4919       return null;
4920     }
4921     if (isEmpty(padStr)) {
4922       padStr = " ";
4923     }
4924     int padLen = padStr.length();
4925     int strLen = str.length();
4926     int pads = size - strLen;
4927     if (pads <= 0) {
4928       return str; // returns original String when possible
4929     }
4930     if (padLen == 1 && pads <= PAD_LIMIT) {
4931       return leftPad(str, size, padStr.charAt(0));
4932     }
4933 
4934     if (pads == padLen) {
4935       return padStr.concat(str);
4936     } else if (pads < padLen) {
4937       return padStr.substring(0, pads).concat(str);
4938     } else {
4939       char[] padding = new char[pads];
4940       char[] padChars = padStr.toCharArray();
4941       for (int i = 0; i < pads; i++) {
4942         padding[i] = padChars[i % padLen];
4943       }
4944       return new String(padding).concat(str);
4945     }
4946   }
4947 
4948   /**
4949    * <p>Gets the substring before the first occurrence of a separator.
4950    * The separator is not returned.</p>
4951    *
4952    * <p>A <code>null</code> string input will return <code>null</code>.
4953    * An empty ("") string input will return the empty string.
4954    * A <code>null</code> separator will return the input string.</p>
4955    *
4956    * <pre>
4957    * StringUtils.substringBefore(null, *)      = null
4958    * StringUtils.substringBefore("", *)        = ""
4959    * StringUtils.substringBefore("abc", "a")   = ""
4960    * StringUtils.substringBefore("abcba", "b") = "a"
4961    * StringUtils.substringBefore("abc", "c")   = "ab"
4962    * StringUtils.substringBefore("abc", "d")   = "abc"
4963    * StringUtils.substringBefore("abc", "")    = ""
4964    * StringUtils.substringBefore("abc", null)  = "abc"
4965    * </pre>
4966    *
4967    * @param str  the String to get a substring from, may be null
4968    * @param separator  the String to search for, may be null
4969    * @return the substring before the first occurrence of the separator,
4970    *  <code>null</code> if null String input
4971    * @since 2.0
4972    */
4973   public static String substringBefore(String str, String separator) {
4974     if (isEmpty(str) || separator == null) {
4975       return str;
4976     }
4977     if (separator.length() == 0) {
4978       return EMPTY;
4979     }
4980     int pos = str.indexOf(separator);
4981     if (pos == -1) {
4982       return str;
4983     }
4984     return str.substring(0, pos);
4985   }
4986 
4987   /**
4988    * <p>Gets the substring after the first occurrence of a separator.
4989    * The separator is not returned.</p>
4990    *
4991    * <p>A <code>null</code> string input will return <code>null</code>.
4992    * An empty ("") string input will return the empty string.
4993    * A <code>null</code> separator will return the empty string if the
4994    * input string is not <code>null</code>.</p>
4995    *
4996    * <pre>
4997    * StringUtils.substringAfter(null, *)      = null
4998    * StringUtils.substringAfter("", *)        = ""
4999    * StringUtils.substringAfter(*, null)      = ""
5000    * StringUtils.substringAfter("abc", "a")   = "bc"
5001    * StringUtils.substringAfter("abcba", "b") = "cba"
5002    * StringUtils.substringAfter("abc", "c")   = ""
5003    * StringUtils.substringAfter("abc", "d")   = ""
5004    * StringUtils.substringAfter("abc", "")    = "abc"
5005    * </pre>
5006    *
5007    * @param str  the String to get a substring from, may be null
5008    * @param separator  the String to search for, may be null
5009    * @return the substring after the first occurrence of the separator,
5010    *  <code>null</code> if null String input
5011    * @since 2.0
5012    */
5013   public static String substringAfter(String str, String separator) {
5014     if (isEmpty(str)) {
5015       return str;
5016     }
5017     if (separator == null) {
5018       return EMPTY;
5019     }
5020     int pos = str.indexOf(separator);
5021     if (pos == -1) {
5022       return EMPTY;
5023     }
5024     return str.substring(pos + separator.length());
5025   }
5026 
5027   /**
5028    * <p>Gets the substring before the last occurrence of a separator.
5029    * The separator is not returned.</p>
5030    *
5031    * <p>A <code>null</code> string input will return <code>null</code>.
5032    * An empty ("") string input will return the empty string.
5033    * An empty or <code>null</code> separator will return the input string.</p>
5034    *
5035    * <pre>
5036    * StringUtils.substringBeforeLast(null, *)      = null
5037    * StringUtils.substringBeforeLast("", *)        = ""
5038    * StringUtils.substringBeforeLast("abcba", "b") = "abc"
5039    * StringUtils.substringBeforeLast("abc", "c")   = "ab"
5040    * StringUtils.substringBeforeLast("a", "a")     = ""
5041    * StringUtils.substringBeforeLast("a", "z")     = "a"
5042    * StringUtils.substringBeforeLast("a", null)    = "a"
5043    * StringUtils.substringBeforeLast("a", "")      = "a"
5044    * </pre>
5045    *
5046    * @param str  the String to get a substring from, may be null
5047    * @param separator  the String to search for, may be null
5048    * @return the substring before the last occurrence of the separator,
5049    *  <code>null</code> if null String input
5050    * @since 2.0
5051    */
5052   public static String substringBeforeLast(String str, String separator) {
5053     if (isEmpty(str) || isEmpty(separator)) {
5054       return str;
5055     }
5056     int pos = str.lastIndexOf(separator);
5057     if (pos == -1) {
5058       return str;
5059     }
5060     return str.substring(0, pos);
5061   }
5062 
5063   /**
5064    * <p>Gets the substring after the last occurrence of a separator.
5065    * The separator is not returned.</p>
5066    *
5067    * <p>A <code>null</code> string input will return <code>null</code>.
5068    * An empty ("") string input will return the empty string.
5069    * An empty or <code>null</code> separator will return the empty string if
5070    * the input string is not <code>null</code>.</p>
5071    *
5072    * <pre>
5073    * StringUtils.substringAfterLast(null, *)      = null
5074    * StringUtils.substringAfterLast("", *)        = ""
5075    * StringUtils.substringAfterLast(*, "")        = ""
5076    * StringUtils.substringAfterLast(*, null)      = ""
5077    * StringUtils.substringAfterLast("abc", "a")   = "bc"
5078    * StringUtils.substringAfterLast("abcba", "b") = "a"
5079    * StringUtils.substringAfterLast("abc", "c")   = ""
5080    * StringUtils.substringAfterLast("a", "a")     = ""
5081    * StringUtils.substringAfterLast("a", "z")     = ""
5082    * </pre>
5083    *
5084    * @param str  the String to get a substring from, may be null
5085    * @param separator  the String to search for, may be null
5086    * @return the substring after the last occurrence of the separator,
5087    *  <code>null</code> if null String input
5088    * @since 2.0
5089    */
5090   public static String substringAfterLast(String str, String separator) {
5091     if (isEmpty(str)) {
5092       return str;
5093     }
5094     if (isEmpty(separator)) {
5095       return EMPTY;
5096     }
5097     int pos = str.lastIndexOf(separator);
5098     if (pos == -1 || pos == (str.length() - separator.length())) {
5099       return EMPTY;
5100     }
5101     return str.substring(pos + separator.length());
5102   }
5103 
5104   /**
5105    * Return the zero element of the list, if it exists, null if the list is empty.
5106    * If there is more than one element in the list, an exception is thrown.
5107    * @param <T>
5108    * @param list is the container of objects to get the first of.
5109    * @return the first object, null, or exception.
5110    */
5111   public static <T> T listPopOne(List<T> list) {
5112     int size = length(list);
5113     if (size == 1) {
5114       return list.get(0);
5115     } else if (size == 0) {
5116       return null;
5117     }
5118     throw new RuntimeException("More than one object of type " + className(list.get(0))
5119         + " was returned when only one was expected. (size:" + size + ")");
5120   }
5121 
5122   /**
5123    * Return the zero element of the set, if it exists, null if the list is empty.
5124    * If there is more than one element in the list, an exception is thrown.
5125    * @param <T>
5126    * @param set is the container of objects to get the first of.
5127    * @return the first object, null, or exception.
5128    */
5129   public static <T> T setPopOne(Set<T> set) {
5130     int size = length(set);
5131     if (size == 1) {
5132       return set.iterator().next();
5133     } else if (size == 0) {
5134       return null;
5135     }
5136     throw new RuntimeException("More than one object of type "
5137         + className(set.iterator().next())
5138         + " was returned when only one was expected. (size:" + size + ")");
5139   }
5140 
5141   /**
5142    * Return the zero element of the list, if it exists, null if the list is empty.
5143    * If there is more than one element in the list, an exception is thrown.
5144    * @param <T>
5145    * @param collection is the container of objects to get the first of.
5146    * @param exceptionIfMoreThanOne will throw exception if there is more than one item in list
5147    * @return the first object, null, or exception.
5148    */
5149   public static <T> T collectionPopOne(Collection<T> collection,
5150       boolean exceptionIfMoreThanOne) {
5151     int size = length(collection);
5152     if (size > 1 && exceptionIfMoreThanOne) {
5153       throw new RuntimeException("More than one object of type "
5154           + className(get(collection, 0))
5155           + " was returned when only one was expected. (size:" + size + ")");
5156     }
5157     if (size == 0) {
5158       return null;
5159     }
5160     return collection.iterator().next();
5161   }
5162 
5163   /**
5164    * Convert an XML string to HTML to display on the screen
5165    * 
5166    * @param input
5167    *          is the XML to convert
5168    * @param isEscape true to escape chars, false to unescape
5169    * 
5170    * @return the HTML converted string
5171    */
5172   public static String xmlEscape(String input, boolean isEscape) {
5173     if (isEscape) {
5174       return replace(input, XML_SEARCH_NO_SINGLE, XML_REPLACE_NO_SINGLE);
5175     }
5176     return replace(input, XML_REPLACE_NO_SINGLE, XML_SEARCH_NO_SINGLE);
5177   }
5178 
5179   /**
5180    * if theString is not blank, apppend it to the result, and if appending,
5181    * @param result to append to
5182    * add a prefix and suffix (if not null)
5183    * @param theStringOrArrayOrList is a string, array, list, or set of strings
5184    * @return true if something appended, false if not
5185    */
5186   public static boolean appendIfNotBlank(StringBuilder result,
5187       Object theStringOrArrayOrList) {
5188     return appendIfNotBlank(result, null, theStringOrArrayOrList, null);
5189   }
5190 
5191   /**
5192    * <pre>
5193    * append a string to another string if both not blank, with separator.  trim to empty everything
5194    * </pre>
5195    * @param string
5196    * @param separator
5197    * @param suffix
5198    * @return the resulting string or blank if nothing
5199    */
5200   public static String appendIfNotBlankString(String string, String separator,
5201       String suffix) {
5202 
5203     string = trimToEmpty(string);
5204     suffix = trimToEmpty(suffix);
5205 
5206     boolean stringIsBlank = isBlank(string);
5207     boolean suffixIsBlank = isBlank(suffix);
5208 
5209     if (stringIsBlank && suffixIsBlank) {
5210       return "";
5211     }
5212 
5213     if (stringIsBlank) {
5214       return suffix;
5215     }
5216 
5217     if (suffixIsBlank) {
5218       return string;
5219     }
5220 
5221     return string + separator + suffix;
5222 
5223   }
5224 
5225   /**
5226    * if theString is not Blank, apppend it to the result, and if appending,
5227    * add a prefix (if not null)
5228    * @param result to append to
5229    * @param prefix
5230    * @param theStringOrArrayOrList is a string, array, list, or set of strings
5231    * @return true if something appended, false if not
5232    */
5233   public static boolean appendIfNotBlank(StringBuilder result,
5234       String prefix, Object theStringOrArrayOrList) {
5235     return appendIfNotBlank(result, prefix, theStringOrArrayOrList, null);
5236   }
5237 
5238   /**
5239    * if theString is not Blank, apppend it to the result, and if appending,
5240    * add a prefix and suffix (if not null)
5241    * @param result to append to, assumed to be not null
5242    * @param prefix
5243    * @param theStringOrArrayOrList is a string, array, list, or set of strings
5244    * @param suffix
5245    * @return true if anything appended, false if not
5246    */
5247   public static boolean appendIfNotBlank(StringBuilder result,
5248       String prefix, Object theStringOrArrayOrList, String suffix) {
5249     return appendIfNotBlank(result, prefix, null, theStringOrArrayOrList, suffix);
5250   }
5251 
5252   /**
5253    * if theString is not Blank, apppend it to the result, and if appending,
5254    * add a prefix and suffix (if not null)
5255    * @param result to append to, assumed to be not null
5256    * @param prefix prepend this prefix always (when a result not empty).  Will be after the other prefix
5257    * @param prefixIfNotBlank prepend this prefix if the result is not empty
5258    * @param theStringOrArrayOrList is a string, array, list, or set of strings
5259    * @param suffix
5260    * @return true if anything appended, false if not
5261    */
5262   public static boolean appendIfNotBlank(StringBuilder result,
5263       String prefix, String prefixIfNotBlank, Object theStringOrArrayOrList, String suffix) {
5264     int length = length(theStringOrArrayOrList);
5265     Iterator iterator = iterator(theStringOrArrayOrList);
5266     boolean appendedAnything = false;
5267 
5268     //these could be appending spaces, so only check to see if they are empty
5269     boolean hasPrefix = !isEmpty(prefix);
5270     boolean hasPrefixIfNotBlank = !isEmpty(prefixIfNotBlank);
5271     boolean hasSuffix = !isEmpty(suffix);
5272     for (int i = 0; i < length; i++) {
5273       String current = (String) next(theStringOrArrayOrList, iterator, i);
5274 
5275       //only append if not empty
5276       if (!isBlank(current)) {
5277 
5278         //keeping track if anything changed
5279         appendedAnything = true;
5280         if (hasPrefix) {
5281           result.append(prefix);
5282         }
5283         if (hasPrefixIfNotBlank && result.length() > 0) {
5284           result.append(prefixIfNotBlank);
5285         }
5286         result.append(current);
5287         if (hasSuffix) {
5288           result.append(suffix);
5289         }
5290       }
5291     }
5292     return appendedAnything;
5293   }
5294 
5295   /**
5296      * if theString is not empty, apppend it to the result, and if appending,
5297      * @param result to append to
5298      * add a prefix and suffix (if not null)
5299      * @param theStringOrArrayOrList is a string, array, list, or set of strings
5300      * @return true if something appended, false if not
5301      */
5302   public static boolean appendIfNotEmpty(StringBuilder result,
5303         Object theStringOrArrayOrList) {
5304     return appendIfNotEmpty(result, null, theStringOrArrayOrList, null);
5305   }
5306 
5307   /**
5308    * if theString is not empty, apppend it to the result, and if appending,
5309    * add a prefix (if not null)
5310    * @param result to append to
5311    * @param prefix
5312    * @param theStringOrArrayOrList is a string, array, list, or set of strings
5313    * @return true if something appended, false if not
5314    */
5315   public static boolean appendIfNotEmpty(StringBuilder result,
5316       String prefix, Object theStringOrArrayOrList) {
5317     return appendIfNotEmpty(result, prefix, theStringOrArrayOrList, null);
5318   }
5319 
5320   /**
5321    * if theString is not empty, apppend it to the result, and if appending,
5322    * add a prefix and suffix (if not null)
5323    * @param result to append to, assumed to be not null
5324    * @param prefix
5325    * @param theStringOrArrayOrList is a string, array, list, or set of strings
5326    * @param suffix
5327    * @return true if anything appended, false if not
5328    */
5329   public static boolean appendIfNotEmpty(StringBuilder result,
5330       String prefix, Object theStringOrArrayOrList, String suffix) {
5331     return appendIfNotEmpty(result, prefix, null, theStringOrArrayOrList, suffix);
5332   }
5333 
5334   /**
5335    * if theString is not empty, apppend it to the result, and if appending,
5336    * add a prefix and suffix (if not null)
5337    * @param result to append to, assumed to be not null
5338    * @param prefix prepend this prefix always (when a result not empty).  Will be after the other prefix
5339    * @param prefixIfNotEmpty prepend this prefix if the result is not empty
5340    * @param theStringOrArrayOrList is a string, array, list, or set of strings
5341    * @param suffix
5342    * @return true if anything appended, false if not
5343    */
5344   public static boolean appendIfNotEmpty(StringBuilder result,
5345       String prefix, String prefixIfNotEmpty, Object theStringOrArrayOrList, String suffix) {
5346     int length = length(theStringOrArrayOrList);
5347     Iterator iterator = iterator(theStringOrArrayOrList);
5348     boolean appendedAnything = false;
5349     boolean hasPrefix = !isEmpty(prefix);
5350     boolean hasPrefixIfNotEmpty = !isEmpty(prefixIfNotEmpty);
5351     boolean hasSuffix = !isEmpty(suffix);
5352     for (int i = 0; i < length; i++) {
5353       String current = (String) next(theStringOrArrayOrList, iterator, i);
5354 
5355       //only append if not empty
5356       if (!isEmpty(current)) {
5357 
5358         //keeping track if anything changed
5359         appendedAnything = true;
5360         if (hasPrefix) {
5361           result.append(prefix);
5362         }
5363         if (hasPrefixIfNotEmpty && result.length() > 0) {
5364           result.append(prefixIfNotEmpty);
5365         }
5366         result.append(current);
5367         if (hasSuffix) {
5368           result.append(suffix);
5369         }
5370       }
5371     }
5372     return appendedAnything;
5373   }
5374 
5375   /**
5376    * <p>Find the index of the given object in the array.</p>
5377    *
5378    * <p>This method returns <code>-1</code> if <code>null</code> array input.</p>
5379    * 
5380    * @param array  the array to search through for the object, may be <code>null</code>
5381    * @param objectToFind  the object to find, may be <code>null</code>
5382    * @return the index of the object within the array, 
5383    *  <code>-1</code> if not found or <code>null</code> array input
5384    */
5385   public static int indexOf(Object[] array, Object objectToFind) {
5386     return indexOf(array, objectToFind, 0);
5387   }
5388 
5389   /**
5390    * <p>Checks if the object is in the given array.</p>
5391    *
5392    * <p>The method returns <code>false</code> if a <code>null</code> array is passed in.</p>
5393    * 
5394    * @param array  the array to search through
5395    * @param objectToFind  the object to find
5396    * @return <code>true</code> if the array contains the object
5397    */
5398   public static boolean contains(Object[] array, Object objectToFind) {
5399     return indexOf(array, objectToFind) != -1;
5400   }
5401 
5402   /**
5403    * <p>Find the index of the given object in the array starting at the given index.</p>
5404    *
5405    * <p>This method returns <code>-1</code> if <code>null</code> array input.</p>
5406    *
5407    * <p>A negative startIndex is treated as zero. A startIndex larger than the array
5408    * length will return <code>-1</code>.</p>
5409    * 
5410    * @param array  the array to search through for the object, may be <code>null</code>
5411    * @param objectToFind  the object to find, may be <code>null</code>
5412    * @param startIndex  the index to start searching at
5413    * @return the index of the object within the array starting at the index,
5414    *  <code>-1</code> if not found or <code>null</code> array input
5415    */
5416   public static int indexOf(Object[] array, Object objectToFind, int startIndex) {
5417     if (array == null) {
5418       return -1;
5419     }
5420     if (startIndex < 0) {
5421       startIndex = 0;
5422     }
5423     if (objectToFind == null) {
5424       for (int i = startIndex; i < array.length; i++) {
5425         if (array[i] == null) {
5426           return i;
5427         }
5428       }
5429     } else {
5430       for (int i = startIndex; i < array.length; i++) {
5431         if (objectToFind.equals(array[i])) {
5432           return i;
5433         }
5434       }
5435     }
5436     return -1;
5437   }
5438 
5439   /**
5440    * Return the zero element of the array, if it exists, null if the array is empty.
5441    * If there is more than one element in the list, an exception is thrown.
5442    * @param <T>
5443    * @param array is the container of objects to get the first of.
5444    * @return the first object, null, or exception.
5445    */
5446   public static <T> T arrayPopOne(T[] array) {
5447     int size = length(array);
5448     if (size == 1) {
5449       return array[0];
5450     } else if (size == 0) {
5451       return null;
5452     }
5453     throw new RuntimeException("More than one object of type " + className(array[0])
5454         + " was returned when only one was expected. (size:" + size + ")");
5455   }
5456 
5457   /**
5458    * Note, this is 
5459    * web service format string
5460    */
5461   private static final String WS_DATE_FORMAT = "yyyy/MM/dd HH:mm:ss.SSS";
5462 
5463   /**
5464    * Note, this is 
5465    * web service format string
5466    */
5467   private static final String WS_DATE_FORMAT2 = "yyyy/MM/dd_HH:mm:ss.SSS";
5468 
5469   /**
5470    * convert a date to a string using the standard web service pattern
5471    * yyyy/MM/dd HH:mm:ss.SSS Note that HH is 0-23
5472    * 
5473    * @param date
5474    * @return the string, or null if the date is null
5475    */
5476   public static String dateToString(Date date) {
5477     if (date == null) {
5478       return null;
5479     }
5480     SimpleDateFormat simpleDateFormat = new SimpleDateFormat(WS_DATE_FORMAT);
5481     return simpleDateFormat.format(date);
5482   }
5483 
5484   /**
5485    * convert a string to a date using the standard web service pattern Note
5486    * that HH is 0-23
5487    * 
5488    * @param dateString
5489    * @return the string, or null if the date was null
5490    */
5491   public static Date stringToDate(String dateString) {
5492     if (isBlank(dateString)) {
5493       return null;
5494     }
5495     SimpleDateFormat simpleDateFormat = new SimpleDateFormat(WS_DATE_FORMAT);
5496     try {
5497       return simpleDateFormat.parse(dateString);
5498     } catch (ParseException e) {
5499       SimpleDateFormat simpleDateFormat2 = new SimpleDateFormat(WS_DATE_FORMAT2);
5500       try {
5501         return simpleDateFormat2.parse(dateString);
5502       } catch (ParseException e2) {
5503         throw new RuntimeException("Cannot convert '" + dateString
5504             + "' to a date based on format: " + WS_DATE_FORMAT, e);
5505       }
5506     }
5507   }
5508 
5509   /**
5510    * @param values
5511    * @return the max long in the list of args
5512    */
5513   public static Long getMaxLongValue(Long... values) {
5514     if (values == null || values.length == 0) {
5515       return null;
5516     }
5517 
5518     Long maxValue = null;
5519     for (int i = 0; i < values.length; i++) {
5520       if (values[i] != null) {
5521         if (maxValue == null || maxValue.compareTo(values[i]) < 0) {
5522           maxValue = new Long(values[i]);
5523         }
5524       }
5525     }
5526 
5527     return maxValue;
5528   }
5529 
5530   /**
5531    * @param values
5532    * @return the min long in the list of args
5533    */
5534   public static Long getMinLongValue(Long... values) {
5535     if (values == null || values.length == 0) {
5536       return null;
5537     }
5538 
5539     Long minValue = null;
5540     for (int i = 0; i < values.length; i++) {
5541       if (values[i] != null) {
5542         if (minValue == null || minValue.compareTo(values[i]) > 0) {
5543           minValue = new Long(values[i]);
5544         }
5545       }
5546     }
5547 
5548     return minValue;
5549   }
5550 
5551   /**
5552    * see if an ip address is on a network
5553    * 
5554    * @param ipString
5555    *          is the ip address to check
5556    * @param networkIpString
5557    *          is the ip address of the network
5558    * @param mask
5559    *          is the length of the mask (0-32)
5560    * @return boolean
5561    */
5562   public static boolean ipOnNetwork(String ipString, String networkIpString, int mask) {
5563 
5564     //this allows all
5565     if (mask == 0) {
5566       return true;
5567     }
5568     int ip = ipInt(ipString);
5569     int networkIp = ipInt(networkIpString);
5570 
5571     ip = ipReadyForAnd(ip, mask);
5572     networkIp = ipReadyForAnd(networkIp, mask);
5573 
5574     return ip == networkIp;
5575   }
5576 
5577   /**
5578    * see if an ip address is on a network
5579    * 
5580    * @param ipString
5581    *          is the ip address to check
5582    * @param networkIpStrings
5583    *          are the ip addresses of the networks, e.g. 1.2.3.4/12, 2.3.4.5/24
5584    * @return boolean
5585    */
5586   public static boolean ipOnNetworks(String ipString, String networkIpStrings) {
5587 
5588     String[] networkIpStringsArray = splitTrim(networkIpStrings, ",");
5589 
5590     //check each one
5591     for (String networkIpString : networkIpStringsArray) {
5592 
5593       if (!contains(networkIpString, "/")) {
5594         throw new RuntimeException(
5595             "String must contain slash and CIDR network bits, e.g. 1.2.3.4/14");
5596       }
5597       //get network part:
5598       String network = prefixOrSuffix(networkIpString, "/", true);
5599       network = trim(network);
5600 
5601       String mask = prefixOrSuffix(networkIpString, "/", false);
5602       mask = trim(mask);
5603       int maskInt = -1;
5604 
5605       maskInt = Integer.parseInt(mask);
5606 
5607       //if on the network, then all good
5608       if (ipOnNetwork(ipString, network, maskInt)) {
5609         return true;
5610       }
5611 
5612     }
5613     return false;
5614   }
5615 
5616   /**
5617    * get the ip address after putting 1's where the subnet mask is not
5618    * @param ip int
5619    * @param maskLength int
5620    * @return int
5621    */
5622   public static int ipReadyForAnd(int ip, int maskLength) {
5623     int mask = -1 + (int) Math.pow(2, 32 - maskLength);
5624 
5625     return ip | mask;
5626   }
5627 
5628   /**
5629    * get the ip addres integer from a string ip address
5630    * @param ip String
5631    * @return int
5632    */
5633   public static int ipInt(String ip) {
5634     int block1;
5635     int block2;
5636     int block3;
5637     int block4;
5638 
5639     try {
5640       int periodIndex = ip.indexOf('.');
5641       String blockString = ip.substring(0, periodIndex);
5642       block1 = Integer.parseInt(blockString);
5643 
5644       //split it up for 2^24 since it does the math wrong if you dont
5645       int mathPow = (int) Math.pow(2, 24);
5646       block1 *= mathPow;
5647 
5648       int oldPeriodIndex = periodIndex;
5649 
5650       periodIndex = ip.indexOf('.', periodIndex + 1);
5651       blockString = ip.substring(oldPeriodIndex + 1, periodIndex);
5652       block2 = Integer.parseInt(blockString);
5653       block2 *= Math.pow(2, 16);
5654       oldPeriodIndex = periodIndex;
5655 
5656       periodIndex = ip.indexOf('.', periodIndex + 1);
5657       blockString = ip.substring(oldPeriodIndex + 1, periodIndex);
5658       block3 = Integer.parseInt(blockString);
5659       block3 *= Math.pow(2, 8);
5660 
5661       blockString = ip.substring(periodIndex + 1, ip.length());
5662       block4 = Integer.parseInt(blockString);
5663     } catch (NumberFormatException nfe) {
5664       throw new RuntimeException("Could not parse the ipaddress: " + ip);
5665     }
5666 
5667     return block1 + block2 + block3 + block4;
5668   }
5669 
5670   /** array for converting HTML to string */
5671   private static final String[] XML_REPLACE_NO_SINGLE = new String[] { "&amp;", "&lt;",
5672       "&gt;", "&quot;" };
5673 
5674   /** array for converting HTML to string */
5675   private static final String[] XML_SEARCH_NO_SINGLE = new String[] { "&", "<", ">", "\"" };
5676 
5677   /**
5678    * <p>A way to get the entire nested stack-trace of an throwable.</p>
5679    *
5680    * @param throwable  the <code>Throwable</code> to be examined
5681    * @return the nested stack trace, with the root cause first
5682    * @since 2.0
5683    */
5684   public static String getFullStackTrace(Throwable throwable) {
5685     StringWriter sw = new StringWriter();
5686     PrintWriter pw = new PrintWriter(sw, true);
5687     Throwable[] ts = getThrowables(throwable);
5688     for (int i = 0; i < ts.length; i++) {
5689       ts[i].printStackTrace(pw);
5690       if (isNestedThrowable(ts[i])) {
5691         break;
5692       }
5693     }
5694     return sw.getBuffer().toString();
5695   }
5696 
5697   /**
5698    * Get a specific index of an array or collection (note for collections and
5699    * iterating, it is more efficient to get an iterator and iterate
5700    * 
5701    * @param arrayOrCollection
5702    * @param index
5703    * @return the object at that index
5704    */
5705   public static Object get(Object arrayOrCollection, int index) {
5706 
5707     if (arrayOrCollection == null) {
5708       if (index == 0) {
5709         return null;
5710       }
5711       throw new RuntimeException("Trying to access index " + index
5712           + " of null");
5713     }
5714 
5715     // no need to iterator on list (e.g. FastProxyList has no iterator
5716     if (arrayOrCollection instanceof List) {
5717       return ((List) arrayOrCollection).get(index);
5718     }
5719     if (arrayOrCollection instanceof Collection) {
5720       Iterator iterator = iterator(arrayOrCollection);
5721       for (int i = 0; i < index; i++) {
5722         next(arrayOrCollection, iterator, i);
5723       }
5724       return next(arrayOrCollection, iterator, index);
5725     }
5726 
5727     if (arrayOrCollection.getClass().isArray()) {
5728       return Array.get(arrayOrCollection, index);
5729     }
5730 
5731     if (index == 0) {
5732       return arrayOrCollection;
5733     }
5734 
5735     throw new RuntimeException("Trying to access index " + index
5736         + " of and object: " + arrayOrCollection);
5737   }
5738 
5739   /**
5740    * <p>Returns the list of <code>Throwable</code> objects in the
5741    * exception chain.</p>
5742    * 
5743    * <p>A throwable without cause will return an array containing
5744    * one element - the input throwable.
5745    * A throwable with one cause will return an array containing
5746    * two elements. - the input throwable and the cause throwable.
5747    * A <code>null</code> throwable will return an array size zero.</p>
5748    *
5749    * @param throwable  the throwable to inspect, may be null
5750    * @return the array of throwables, never null
5751    */
5752   @SuppressWarnings("unchecked")
5753   public static Throwable[] getThrowables(Throwable throwable) {
5754     List list = new ArrayList();
5755     while (throwable != null) {
5756       list.add(throwable);
5757       throwable = getCause(throwable);
5758     }
5759     return (Throwable[]) list.toArray(new Throwable[list.size()]);
5760   }
5761 
5762   /**
5763    * <p>Checks if the Throwable class has a <code>getCause</code> method.</p>
5764    * 
5765    * <p>This is true for JDK 1.4 and above.</p>
5766    * 
5767    * @return true if Throwable is nestable
5768    * @since 2.0
5769    */
5770   public static boolean isThrowableNested() {
5771     return THROWABLE_CAUSE_METHOD != null;
5772   }
5773 
5774   /**
5775    * <p>Checks whether this <code>Throwable</code> class can store a cause.</p>
5776    * 
5777    * <p>This method does <b>not</b> check whether it actually does store a cause.<p>
5778    *
5779    * @param throwable  the <code>Throwable</code> to examine, may be null
5780    * @return boolean <code>true</code> if nested otherwise <code>false</code>
5781    * @since 2.0
5782    */
5783   @SuppressWarnings("unchecked")
5784   public static boolean isNestedThrowable(Throwable throwable) {
5785     if (throwable == null) {
5786       return false;
5787     }
5788 
5789 //    if (throwable instanceof Nestable) {
5790 //      return true;
5791 /*    } else */ if (throwable instanceof SQLException) {
5792       return true;
5793     } else if (throwable instanceof InvocationTargetException) {
5794       return true;
5795     } else if (isThrowableNested()) {
5796       return true;
5797     }
5798 
5799     Class cls = throwable.getClass();
5800     for (int i = 0, isize = CAUSE_METHOD_NAMES.length; i < isize; i++) {
5801       try {
5802         Method method = cls.getMethod(CAUSE_METHOD_NAMES[i], (Class[]) null);
5803         if (method != null && Throwable.class.isAssignableFrom(method.getReturnType())) {
5804           return true;
5805         }
5806       } catch (NoSuchMethodException ignored) {
5807       } catch (SecurityException ignored) {
5808       }
5809     }
5810 
5811     try {
5812       Field field = cls.getField("detail");
5813       if (field != null) {
5814         return true;
5815       }
5816     } catch (NoSuchFieldException ignored) {
5817     } catch (SecurityException ignored) {
5818     }
5819 
5820     return false;
5821   }
5822 
5823   /**
5824    * <p>The Method object for JDK1.4 getCause.</p>
5825    */
5826   private static final Method THROWABLE_CAUSE_METHOD;
5827 
5828   static {
5829     Method getCauseMethod;
5830     try {
5831       getCauseMethod = Throwable.class.getMethod("getCause", (Class[]) null);
5832     } catch (Exception e) {
5833       getCauseMethod = null;
5834     }
5835     THROWABLE_CAUSE_METHOD = getCauseMethod;
5836   }
5837 
5838   /**
5839    * <p>Introspects the <code>Throwable</code> to obtain the cause.</p>
5840    * 
5841    * <p>The method searches for methods with specific names that return a 
5842    * <code>Throwable</code> object. This will pick up most wrapping exceptions,
5843    * including those from JDK 1.4, and</p>
5844    *
5845    * <p>The default list searched for are:</p>
5846    * <ul>
5847    *  <li><code>getCause()</code></li>
5848    *  <li><code>getNextException()</code></li>
5849    *  <li><code>getTargetException()</code></li>
5850    *  <li><code>getException()</code></li>
5851    *  <li><code>getSourceException()</code></li>
5852    *  <li><code>getRootCause()</code></li>
5853    *  <li><code>getCausedByException()</code></li>
5854    *  <li><code>getNested()</code></li>
5855    * </ul>
5856    * 
5857    * <p>In the absence of any such method, the object is inspected for a
5858    * <code>detail</code> field assignable to a <code>Throwable</code>.</p>
5859    * 
5860    * <p>If none of the above is found, returns <code>null</code>.</p>
5861    *
5862    * @param throwable  the throwable to introspect for a cause, may be null
5863    * @return the cause of the <code>Throwable</code>,
5864    *  <code>null</code> if none found or null throwable input
5865    * @since 1.0
5866    */
5867   public static Throwable getCause(Throwable throwable) {
5868     return getCause(throwable, CAUSE_METHOD_NAMES);
5869   }
5870 
5871   /**
5872    * <p>The names of methods commonly used to access a wrapped exception.</p>
5873    */
5874   private static String[] CAUSE_METHOD_NAMES = {
5875       "getCause",
5876       "getNextException",
5877       "getTargetException",
5878       "getException",
5879       "getSourceException",
5880       "getRootCause",
5881       "getCausedByException",
5882       "getNested",
5883       "getLinkedException",
5884       "getNestedException",
5885       "getLinkedCause",
5886       "getThrowable",
5887   };
5888 
5889   /**
5890    * <p>Introspects the <code>Throwable</code> to obtain the cause.</p>
5891    * 
5892    * <ol>
5893    * <li>Try known exception types.</li>
5894    * <li>Try the supplied array of method names.</li>
5895    * <li>Try the field 'detail'.</li>
5896    * </ol>
5897    * 
5898    * <p>A <code>null</code> set of method names means use the default set.
5899    * A <code>null</code> in the set of method names will be ignored.</p>
5900    *
5901    * @param throwable  the throwable to introspect for a cause, may be null
5902    * @param methodNames  the method names, null treated as default set
5903    * @return the cause of the <code>Throwable</code>,
5904    *  <code>null</code> if none found or null throwable input
5905    * @since 1.0
5906    */
5907   public static Throwable getCause(Throwable throwable, String[] methodNames) {
5908     if (throwable == null) {
5909       return null;
5910     }
5911     Throwable cause = getCauseUsingWellKnownTypes(throwable);
5912     if (cause == null) {
5913       if (methodNames == null) {
5914         methodNames = CAUSE_METHOD_NAMES;
5915       }
5916       for (int i = 0; i < methodNames.length; i++) {
5917         String methodName = methodNames[i];
5918         if (methodName != null) {
5919           cause = getCauseUsingMethodName(throwable, methodName);
5920           if (cause != null) {
5921             break;
5922           }
5923         }
5924       }
5925 
5926       if (cause == null) {
5927         cause = getCauseUsingFieldName(throwable, "detail");
5928       }
5929     }
5930     return cause;
5931   }
5932 
5933   /**
5934    * <p>Finds a <code>Throwable</code> by field name.</p>
5935    * 
5936    * @param throwable  the exception to examine
5937    * @param fieldName  the name of the attribute to examine
5938    * @return the wrapped exception, or <code>null</code> if not found
5939    */
5940   private static Throwable getCauseUsingFieldName(Throwable throwable, String fieldName) {
5941     Field field = null;
5942     try {
5943       field = throwable.getClass().getField(fieldName);
5944     } catch (NoSuchFieldException ignored) {
5945     } catch (SecurityException ignored) {
5946     }
5947 
5948     if (field != null && Throwable.class.isAssignableFrom(field.getType())) {
5949       try {
5950         return (Throwable) field.get(throwable);
5951       } catch (IllegalAccessException ignored) {
5952       } catch (IllegalArgumentException ignored) {
5953       }
5954     }
5955     return null;
5956   }
5957 
5958   /**
5959    * <p>Finds a <code>Throwable</code> by method name.</p>
5960    * 
5961    * @param throwable  the exception to examine
5962    * @param methodName  the name of the method to find and invoke
5963    * @return the wrapped exception, or <code>null</code> if not found
5964    */
5965   private static Throwable getCauseUsingMethodName(Throwable throwable, String methodName) {
5966     Method method = null;
5967     try {
5968       method = throwable.getClass().getMethod(methodName, (Class[]) null);
5969     } catch (NoSuchMethodException ignored) {
5970     } catch (SecurityException ignored) {
5971     }
5972 
5973     if (method != null && Throwable.class.isAssignableFrom(method.getReturnType())) {
5974       try {
5975         return (Throwable) method.invoke(throwable, EMPTY_OBJECT_ARRAY);
5976       } catch (IllegalAccessException ignored) {
5977       } catch (IllegalArgumentException ignored) {
5978       } catch (InvocationTargetException ignored) {
5979       }
5980     }
5981     return null;
5982   }
5983 
5984   /**
5985    * <p>Finds a <code>Throwable</code> for known types.</p>
5986    * 
5987    * <p>Uses <code>instanceof</code> checks to examine the exception,
5988    * looking for well known types which could contain chained or
5989    * wrapped exceptions.</p>
5990    *
5991    * @param throwable  the exception to examine
5992    * @return the wrapped exception, or <code>null</code> if not found
5993    */
5994   private static Throwable getCauseUsingWellKnownTypes(Throwable throwable) {
5995     /* if (throwable instanceof Nestable) {
5996       return ((Nestable) throwable).getCause();
5997     } else */ if (throwable instanceof SQLException) {
5998       return ((SQLException) throwable).getNextException();
5999     } else if (throwable instanceof InvocationTargetException) {
6000       return ((InvocationTargetException) throwable).getTargetException();
6001     } else {
6002       return null;
6003     }
6004   }
6005 
6006 }