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