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  package edu.internet2.middleware.grouperClient.config;
17  
18  import java.io.BufferedReader;
19  import java.io.File;
20  import java.io.FileInputStream;
21  import java.io.FileOutputStream;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.InputStreamReader;
25  import java.io.ObjectInputStream;
26  import java.io.ObjectOutputStream;
27  import java.io.OutputStream;
28  import java.io.PrintWriter;
29  import java.io.PushbackInputStream;
30  import java.io.Reader;
31  import java.io.Serializable;
32  import java.io.StringWriter;
33  import java.io.UnsupportedEncodingException;
34  import java.io.Writer;
35  import java.lang.annotation.Annotation;
36  import java.lang.reflect.Array;
37  import java.lang.reflect.Constructor;
38  import java.lang.reflect.Field;
39  import java.lang.reflect.InvocationTargetException;
40  import java.lang.reflect.Method;
41  import java.lang.reflect.Modifier;
42  import java.math.BigDecimal;
43  import java.net.InetAddress;
44  import java.net.URL;
45  import java.net.URLDecoder;
46  import java.net.URLEncoder;
47  import java.security.CodeSource;
48  import java.sql.Connection;
49  import java.sql.ResultSet;
50  import java.sql.SQLException;
51  import java.sql.Statement;
52  import java.sql.Timestamp;
53  import java.text.DateFormat;
54  import java.text.DecimalFormat;
55  import java.text.ParseException;
56  import java.text.SimpleDateFormat;
57  import java.util.ArrayList;
58  import java.util.Arrays;
59  import java.util.Calendar;
60  import java.util.Collection;
61  import java.util.Collections;
62  import java.util.Date;
63  import java.util.HashMap;
64  import java.util.HashSet;
65  import java.util.Iterator;
66  import java.util.LinkedHashMap;
67  import java.util.LinkedHashSet;
68  import java.util.List;
69  import java.util.Map;
70  import java.util.Properties;
71  import java.util.Set;
72  import java.util.concurrent.Callable;
73  import java.util.concurrent.ExecutorService;
74  import java.util.concurrent.Executors;
75  import java.util.concurrent.Future;
76  import java.util.concurrent.ThreadFactory;
77  import java.util.regex.Matcher;
78  import java.util.regex.Pattern;
79  
80  import edu.internet2.middleware.grouperClient.util.ExpirableCache;
81  import edu.internet2.middleware.grouperClient.util.GrouperClientLog;
82  
83  
84  
85  /**
86   * utility methods for grouper.
87   * @author mchyzer
88   *
89   */
90  @SuppressWarnings({ "serial", "unchecked" })
91  public class ConfigPropertiesCascadeCommonUtils  {
92  
93    /** override map for properties in thread local to be used in a web server or the like */
94    private static ThreadLocal<Map<String, Map<String, String>>> propertiesThreadLocalOverrideMap = new InheritableThreadLocal<Map<String, Map<String, String>>>();
95  
96    /**
97     * return the arg after the argBefore, or null if not there, or exception
98     * if argBefore is not found
99     * @param args
100    * @param argBefore
101    * @return the arg
102    */
103   public static String argAfter(String[] args, String argBefore) {
104     if (length(args) <= 1) {
105       return null;
106     }
107     int argBeforeIndex = -1;
108     for (int i=0;i<args.length;i++) {
109       if (equals(args[i], argBefore)) {
110         argBeforeIndex = i;
111         break;
112       }
113     }
114     if (argBeforeIndex == -1) {
115       throw new RuntimeException("Cant find arg before");
116     }
117     if (argBeforeIndex < args.length - 1) {
118       return args[argBeforeIndex + 1];
119     }
120     return null;
121   }
122   
123   /**
124    * append and maybe put a separator in there
125    * @param result
126    * @param separatorIfResultNotEmpty
127    * @param stringToAppend
128    */
129   public static void append(StringBuilder result, 
130       String separatorIfResultNotEmpty, String stringToAppend) {
131     if (result.length() != 0) {
132       result.append(separatorIfResultNotEmpty);
133     }
134     result.append(stringToAppend);
135   }
136   
137   /**
138    * 
139    */
140   public static final String LOG_ERROR = "Error trying to make parent dirs for logger or logging first statement, check to make " +
141                 "sure you have proper file permissions, and that your servlet container is giving " +
142                 "your app rights to access the log directory (e.g. for tomcat set TOMCAT5_SECURITY=no), g" +
143                 "oogle it for more info";
144 
145   /**
146    * The number of bytes in a kilobyte.
147    */
148   public static final long ONE_KB = 1024;
149 
150   /**
151    * The number of bytes in a megabyte.
152    */
153   public static final long ONE_MB = ONE_KB * ONE_KB;
154 
155   /**
156    * The number of bytes in a gigabyte.
157    */
158   public static final long ONE_GB = ONE_KB * ONE_MB;
159 
160   /**
161    * Returns a human-readable version of the file size (original is in
162    * bytes).
163    *
164    * @param size The number of bytes.
165    * @return     A human-readable display value (includes units).
166    * @todo need for I18N?
167    */
168   public static String byteCountToDisplaySize(long size) {
169     String displaySize;
170 
171     if (size / ONE_GB > 0) {
172       displaySize = String.valueOf(size / ONE_GB) + " GB";
173     } else if (size / ONE_MB > 0) {
174       displaySize = String.valueOf(size / ONE_MB) + " MB";
175     } else if (size / ONE_KB > 0) {
176       displaySize = String.valueOf(size / ONE_KB) + " KB";
177     } else {
178       displaySize = String.valueOf(size) + " bytes";
179     }
180 
181     return displaySize;
182   }
183   /**
184    * see if options have a specific option by int bits
185    * @param options
186    * @param option
187    * @return if the option is there
188    */
189   public static boolean hasOption(int options, int option) {
190     return (options & option) > 0;
191   }
192   
193   /**
194    * get canonical path of file
195    * @param file
196    * @return the path
197    */
198   public static String fileCanonicalPath(File file) {
199     try {
200       return file.getCanonicalPath();
201     } catch (IOException ioe) {
202       throw new RuntimeException(ioe);
203     }
204   }
205   
206   /**
207    * return the suffix after a char.  If the char doesnt exist, just return the string
208    * @param input string
209    * @param theChar char
210    * @return new string
211    */
212   public static String suffixAfterChar(String input, char theChar) {
213     if (input == null) {
214       return null;
215     }
216     //get the real type off the end
217     int lastIndex = input.lastIndexOf(theChar);
218     if (lastIndex > -1) {
219       input = input.substring(lastIndex + 1, input.length());
220     }
221     return input;
222   }
223 
224   /**
225    * get the oracle underscore name e.g. javaNameHere -> JAVA_NAME_HERE
226    *
227    * @param javaName
228    *          the java convention name
229    *
230    * @return the oracle underscore name based on the java name
231    */
232   public static String oracleStandardNameFromJava(String javaName) {
233   
234     StringBuilder result = new StringBuilder();
235   
236     if ((javaName == null) || (0 == "".compareTo(javaName))) {
237       return javaName;
238     }
239   
240     //if package is specified, only look at class name
241     javaName = suffixAfterChar(javaName, '.');
242   
243     //dont check the first char
244     result.append(javaName.charAt(0));
245   
246     char currChar;
247   
248     boolean previousCap = false;
249     
250     //loop through the string, looking for uppercase
251     for (int i = 1; i < javaName.length(); i++) {
252       currChar = javaName.charAt(i);
253   
254       //if uppcase append an underscore
255       if (!previousCap && (currChar >= 'A') && (currChar <= 'Z')) {
256         result.append("_");
257       }
258   
259       result.append(currChar);
260       if ((currChar >= 'A') && (currChar <= 'Z')) {
261         previousCap = true;
262       } else {
263         previousCap = false;
264       }
265     }
266   
267     //this is in upper-case
268     return result.toString().toUpperCase();
269   }
270 
271   
272   /**
273    * see if two maps are the equivalent (based on number of entries, 
274    * and the equals() method of the keys and values)
275    * @param <K> 
276    * @param <V> 
277    * @param first
278    * @param second
279    * @return true if equal
280    */
281   public static <K,V> boolean mapEquals(Map<K,V> first, Map<K,V> second) {
282     Set<K> keysMismatch = new HashSet<K>();
283     mapDifferences(first, second, keysMismatch, null);
284     //if any keys mismatch, then not equal
285     return keysMismatch.size() == 0;
286     
287   }
288   
289   /**
290    * empty map
291    */
292   private static final Map EMPTY_MAP = Collections.unmodifiableMap(new HashMap());
293   
294   /**
295    * see if two maps are the equivalent (based on number of entries, 
296    * and the equals() method of the keys and values)
297    * @param <K> 
298    * @param <V> 
299    * @param first map to check diffs
300    * @param second map to check diffs
301    * @param differences set of keys (with prefix) of the diffs
302    * @param prefix for the entries in the diffs (e.g. "attribute__"
303    */
304   @SuppressWarnings("unchecked")
305   public static <K,V> void mapDifferences(Map<K,V> first, Map<K,V> second, Set<K> differences, String prefix) {
306     if (first == second) {
307       return;
308     }
309     //put the collections in new collections so we can remove and keep track
310     if (first == null) {
311       first = EMPTY_MAP;
312     }
313     if (second == null) {
314       second = EMPTY_MAP;
315     } else {
316       //make linked so the results are ordered
317       second = new LinkedHashMap<K,V>(second);
318     }
319     int firstSize = first == null ? 0 : first.size();
320     int secondSize = second == null ? 0 : second.size();
321     //if both empty then all good
322     if (firstSize == 0 && secondSize == 0) {
323       return;
324     }
325    
326     for (K key : first.keySet()) {
327 
328       if (second.containsKey(key)) {
329         V firstValue = first.get(key);
330         V secondValue = second.get(key);
331         //keep track by removing from second
332         second.remove(key);
333         if (equals(firstValue, secondValue)) {
334           continue;
335         }
336       }
337       differences.add(isNotBlank(prefix) ? (K)(prefix + key) : key);
338     }
339     //add the ones left over in the second map which are not in the first map
340     for (K key : second.keySet()) {
341       differences.add(isNotBlank(prefix) ? (K)(prefix + key) : key);
342     }
343   }
344   
345   /**
346    * sleep, if interrupted, throw runtime
347    * @param millis
348    */
349   public static void sleep(long millis) {
350     try {
351       Thread.sleep(millis);
352     } catch (InterruptedException ie) {
353       throw new RuntimeException(ie);
354     }
355   }
356   
357   /**
358    * If we can, inject this into the exception, else return false
359    * @param t
360    * @param message
361    * @return true if success, false if not
362    */
363   public static boolean injectInException(Throwable t, String message) {
364     
365     String throwableFieldName = "detailMessage";
366 
367     try {
368       String currentValue = t.getMessage();
369       if (!isBlank(currentValue)) {
370         currentValue += ",\n" + message;
371       } else {
372         currentValue = message;
373       }
374       assignField(t, throwableFieldName, currentValue);
375       return true;
376     } catch (Throwable t2) {
377       //dont worry about what the problem is, return false so the caller can log
378       return false;
379     }
380     
381   }
382   
383   /**
384    * get a unique string identifier based on the current time,
385    * this is not globally unique, just unique for as long as this
386    * server is running...
387    * 
388    * @return String
389    */
390   public static String uniqueId() {
391     //this needs to be threadsafe since we are using a static field
392     synchronized (ConfigPropertiesCascadeCommonUtils.class) {
393       lastId = incrementStringInt(lastId);
394     }
395 
396     return String.valueOf(lastId);
397   }
398 
399   /**
400    * get a file name from a resource name
401    * 
402    * @param resourceName
403    *          is the classpath location
404    * 
405    * @return the file path on the system
406    */
407   public static File fileFromResourceName(String resourceName) {
408     
409     URL url = computeUrl(resourceName, true);
410 
411     if (url == null) {
412       return null;
413     }
414 
415     try {
416       String fileName = URLDecoder.decode(url.getFile(), "UTF-8");
417   
418       File configFile = new File(fileName);
419 
420       return configFile;
421     } catch (UnsupportedEncodingException uee) {
422       throw new RuntimeException(uee);
423     }
424   }
425   
426 
427   /**
428    * compute a url of a resource
429    * @param resourceName
430    * @param canBeNull if cant be null, throw runtime
431    * @return the URL
432    */
433   public static URL computeUrl(String resourceName, boolean canBeNull) {
434     //get the url of the navigation file
435     ClassLoader cl = classLoader();
436 
437     URL url = null;
438 
439     try {
440       //CH 20081012: sometimes it starts with slash and it shouldnt...
441       String newResourceName = resourceName.startsWith("/") 
442         ? resourceName.substring(1) : resourceName;
443       url = cl.getResource(newResourceName);
444     } catch (NullPointerException npe) {
445       String error = "computeUrl() Could not find resource file: " + resourceName;
446       throw new RuntimeException(error, npe);
447     }
448 
449     if (!canBeNull && url == null) {
450       throw new RuntimeException("Cant find resource: " + resourceName);
451     }
452 
453     return url;
454   }
455 
456 
457   /**
458    * fast class loader
459    * @return the class loader
460    */
461   public static ClassLoader classLoader() {
462     return ConfigPropertiesCascadeCommonUtils.class.getClassLoader();
463   }
464 
465   /**
466    * make sure a array is non null.  If null, then return an empty array.
467    * Note: this will probably not work for primitive arrays (e.g. int[])
468    * @param <T>
469    * @param array
470    * @param theClass to make array from
471    * @return the list or empty list if null
472    */
473   @SuppressWarnings("unchecked")
474   public static <T> T[] nonNull(T[] array, Class<?> theClass) {
475     return array == null ? ((T[])Array.newInstance(theClass, 0)) : array;
476   }
477   
478   /**
479    * get the prefix or suffix of a string based on a separator
480    * 
481    * @param startString
482    *          is the string to start with
483    * @param separator
484    *          is the separator to split on
485    * @param isPrefix
486    *          if thre prefix or suffix should be returned
487    * 
488    * @return the prefix or suffix, if the separator isnt there, return the
489    *         original string
490    */
491   public static String prefixOrSuffix(String startString, String separator,
492       boolean isPrefix) {
493     String prefixOrSuffix = null;
494 
495     //no nulls
496     if (startString == null) {
497       return startString;
498     }
499 
500     //where is the separator
501     int separatorIndex = startString.indexOf(separator);
502 
503     //if none exists, dont proceed
504     if (separatorIndex == -1) {
505       return startString;
506     }
507 
508     //maybe the separator isnt on character
509     int separatorLength = separator.length();
510 
511     if (isPrefix) {
512       prefixOrSuffix = startString.substring(0, separatorIndex);
513     } else {
514       prefixOrSuffix = startString.substring(separatorIndex + separatorLength,
515           startString.length());
516     }
517 
518     return prefixOrSuffix;
519   }
520 
521   /**
522    * get the extension from name.  if name is a:b:c, name is c
523    * @param name
524    * @return the name
525    */
526   public static String extensionFromName(String name) {
527     if (isBlank(name)) {
528       return name;
529     }
530     int lastColonIndex = name.lastIndexOf(':');
531     if (lastColonIndex == -1) {
532       return name;
533     }
534     String extension = name.substring(lastColonIndex+1);
535     return extension;
536   }
537   
538   /**
539    * <pre>Returns the class object.</pre>
540    * @param origClassName is fully qualified
541    * @return the class
542    */
543   public static Class forName(String origClassName) {
544         
545     try {
546       return Class.forName(origClassName);
547     } catch (Throwable t) {
548       throw new RuntimeException("Problem loading class: " + origClassName, t);
549     }
550     
551   }
552   
553   /**
554    * Construct a class
555    * @param <T> template type
556    * @param theClass
557    * @return the instance
558    */
559   public static <T> T newInstance(Class<T> theClass) {
560     try {
561       return theClass.newInstance();
562     } catch (Throwable e) {
563       if (theClass != null && Modifier.isAbstract(theClass.getModifiers())) {
564         throw new RuntimeException("Problem with class: " + theClass + ", maybe because it is abstract!", e);        
565       }
566       throw new RuntimeException("Problem with class: " + theClass, e);
567     }
568   }
569   
570   /**
571    * Construct a class
572    * @param <T> template type
573    * @param theClass
574    * @param allowPrivateConstructor true if should allow private constructors
575    * @return the instance
576    */
577   public static <T> T newInstance(Class<T> theClass, boolean allowPrivateConstructor) {
578     if (!allowPrivateConstructor) {
579       return newInstance(theClass);
580     }
581     try {
582       Constructor<?>[] constructorArray = theClass.getDeclaredConstructors();
583       for (Constructor<?> constructor : constructorArray) {
584          if (constructor.getGenericParameterTypes().length == 0) {
585            if (allowPrivateConstructor) {
586              constructor.setAccessible(true);
587            }
588            return (T)constructor.newInstance();
589          }
590       }
591       //why cant we find a constructor???
592       throw new RuntimeException("Why cant we find a constructor for class: " + theClass);
593     } catch (Throwable e) {
594       if (theClass != null && Modifier.isAbstract(theClass.getModifiers())) {
595         throw new RuntimeException("Problem with class: " + theClass + ", maybe because it is abstract!", e);        
596       }
597       throw new RuntimeException("Problem with class: " + theClass, e);
598     }
599   }
600   
601   /**
602    * get the parent stem name from name.  if already a root stem
603    * then just return null.  e.g. if the name is a:b:c then
604    * the return value is a:b
605    * @param name
606    * @return the parent stem name or null if none
607    */
608   public static String parentStemNameFromName(String name) {
609     int lastColonIndex = name.lastIndexOf(':');
610     if (lastColonIndex == -1) {
611       return null;
612     }
613     String parentStemName = name.substring(0,lastColonIndex);
614     return parentStemName;
615 
616   }
617   
618   /**
619    * return the string or the other if the first is blank
620    * @param string
621    * @param defaultStringIfBlank
622    * @return the string or the default one
623    */
624   public static String defaultIfBlank(String string, String defaultStringIfBlank) {
625     return isBlank(string) ? defaultStringIfBlank : string;
626   }
627   
628   /**
629    * genericized method to see if first is null, if so then return second, else first.
630    * @param <T>
631    * @param theValue first input
632    * @param defaultIfTheValueIsNull second input
633    * @return the first if not null, second if no
634    */
635   public static <T> T defaultIfNull(T theValue, T defaultIfTheValueIsNull) {
636     return theValue != null ? theValue : defaultIfTheValueIsNull;
637   }
638   
639   /**
640    * add each element of listToAdd if it is not already in list
641    * @param <T>
642    * @param list to add to
643    * @param listToAdd each element will be added to list, or null if none
644    */
645   public static <T> void addIfNotThere(Collection<T> list, Collection<T> listToAdd) {
646     //maybe nothing to do
647     if (listToAdd == null) {
648       return;
649     }
650     for (T t : listToAdd) {
651       if (!list.contains(t)) {
652         list.add(t);
653       }
654     }
655   }
656 
657   
658   /**
659    * print out various types of objects
660    * 
661    * @param object
662    * @param maxChars is where it should stop when figuring out object.  note, result might be longer than max...
663    * need to abbreviate when back
664    * @param result is where to append to
665    */
666   @SuppressWarnings("unchecked")
667   private static void toStringForLogHelper(Object object, int maxChars, StringBuilder result) {
668     
669     try {
670       if (object == null) {
671         result.append("null");
672       } else if (object.getClass().isArray()) {
673         // handle arrays
674         int length = Array.getLength(object);
675         if (length == 0) {
676           result.append("Empty array");
677         } else {
678           result.append("Array size: ").append(length).append(": ");
679           for (int i = 0; i < length; i++) {
680             result.append("[").append(i).append("]: ").append(
681                 Array.get(object, i)).append("\n");
682             if (maxChars != -1 && result.length() > maxChars) {
683               return;
684             }
685           }
686         }
687       } else if (object instanceof Collection) {
688         //give size and type if collection
689         Collection<Object> collection = (Collection<Object>) object;
690         int collectionSize = collection.size();
691         if (collectionSize == 0) {
692           result.append("Empty ").append(object.getClass().getSimpleName());
693         } else {
694           result.append(object.getClass().getSimpleName()).append(" size: ").append(collectionSize).append(": ");
695           int i=0;
696           for (Object collectionObject : collection) {
697             result.append("[").append(i).append("]: ").append(
698                 collectionObject).append("\n");
699             if (maxChars != -1 && result.length() > maxChars) {
700               return;
701             }
702             i++;
703           }
704         }
705       } else {
706         result.append(object.toString());
707       }
708     } catch (Exception e) {
709       result.append("<<exception>> ").append(object.getClass()).append(":\n")
710         .append(getFullStackTrace(e)).append("\n");
711     }
712   }
713 
714   /**
715    * convert a set to a string (comma separate)
716    * @param set
717    * @return the String
718    */
719   public static String setToString(Set set) {
720     if (set == null) {
721       return "null";
722     }
723     if (set.size() == 0) {
724       return "empty";
725     }
726     StringBuilder result = new StringBuilder();
727     boolean first = true;
728     for (Object object : set) {
729       if (!first) {
730         result.append(", ");
731       }
732       first = false;
733       result.append(object);
734     }
735     return result.toString();
736   }
737   
738   /**
739    * convert a set to a string (comma separate)
740    * @param map
741    * @return the String
742    * @deprecated use mapToString(map)
743    */
744   @Deprecated
745   public static String MapToString(Map map) {
746     return mapToString(map);
747   }
748 
749   /**
750    * convert a set to a string (comma separate)
751    * @param map
752    * @return the String
753    */
754   public static String mapToString(Map map) {
755     if (map == null) {
756       return "null";
757     }
758     if (map.size() == 0) {
759       return "empty";
760     }
761     StringBuilder result = new StringBuilder();
762     boolean first = true;
763     for (Object object : map.keySet()) {
764       if (!first) {
765         result.append(", ");
766       }
767       first = false;
768       result.append(object).append(": ").append(map.get(object));
769     }
770     return result.toString();
771   }
772 
773   /**
774    * print out various types of objects
775    * 
776    * @param object
777    * @return the string value
778    */
779   public static String toStringForLog(Object object) {
780     StringBuilder result = new StringBuilder();
781     toStringForLogHelper(object, -1, result);
782     return result.toString();
783   }
784 
785   /**
786    * print out various types of objects
787    * 
788    * @param object
789    * @param maxChars is the max chars that should be returned (abbreviate if longer), or -1 for any amount
790    * @return the string value
791    */
792   public static String toStringForLog(Object object, int maxChars) {
793     StringBuilder result = new StringBuilder();
794     toStringForLogHelper(object, -1, result);
795     String resultString = result.toString();
796     if (maxChars != -1) {
797       return abbreviate(resultString, maxChars);
798     }
799     return resultString;
800   }
801 
802   /**
803    * If batching this is the number of batches
804    * @param count is size of set
805    * @param batchSize
806    * @return the number of batches
807    * @param haveAtLeastOne is true if there should be at least one run even if the collection is empty (e.g. for queries based on other things)
808    */
809   public static int batchNumberOfBatches(int count, int batchSize, boolean haveAtLeastOne) {
810     if (!haveAtLeastOne && count == 0) {
811       return 0;
812     }
813 
814     //not sure why this would be 0...
815     if (batchSize == 0) {
816       return 0;
817     }
818     int batches = 1 + ((count - 1) / batchSize);
819     return batches;
820 
821   }
822 
823   /**
824    * If batching this is the number of batches.  Will return at least 1
825    * @param count is size of set
826    * @param batchSize
827    * @return the number of batches
828    * @deprecated use batchNumberOfBatches(Collection<?> collection, int batchSize, boolean haveAtLeastOne)
829    */
830   @Deprecated
831   public static int batchNumberOfBatches(int count, int batchSize) {
832     return batchNumberOfBatches(count, batchSize, true);
833   }
834 
835   /**
836    * If batching this is the number of batches, will return at least 1
837    * @param collection
838    * @param batchSize
839    * @return the number of batches
840    * @deprecated use batchNumberOfBatches(Collection<?> collection, int batchSize, boolean haveAtLeastOne)
841    */
842   @Deprecated
843   public static int batchNumberOfBatches(Collection<?> collection, int batchSize) {
844     return batchNumberOfBatches(collection, batchSize, true);
845   }
846 
847   /**
848    * If batching this is the number of batches, will return at least 1
849    * @param collection
850    * @param batchSize
851    * @param haveAtLeastOne is true if there should be at least one run even if the collection is empty (e.g. for queries based on other things)
852    * @return the number of batches
853    */
854   public static int batchNumberOfBatches(Collection<?> collection, int batchSize, boolean haveAtLeastOne) {
855     int arrraySize = length(collection);
856     return batchNumberOfBatches(arrraySize, batchSize, haveAtLeastOne);
857 
858   }
859   /**
860    * retrieve a batch by 0 index. Will return an array of size batchSize or
861    * the remainder. the array will be full of elements. Note, this requires an
862    * ordered input (so use linkedhashset not hashset if doing sets)
863    * @param <T> template type
864    * @param collection
865    * @param batchSize
866    * @param batchIndex
867    * @return the list
868    *         This never returns null, only empty list
869    */
870   @SuppressWarnings("unchecked")
871   public static <T> List<T> batchList(Collection<T> collection, int batchSize,
872       int batchIndex) {
873 
874     int numberOfBatches = batchNumberOfBatches(collection, batchSize);
875     int arraySize = length(collection);
876 
877     // short circuit
878     if (arraySize == 0) {
879       return new ArrayList<T>();
880     }
881 
882     List<T> theBatchObjects = new ArrayList<T>();
883 
884     // lets get the type of the first element if possible
885 //    Object first = get(arrayOrCollection, 0);
886 //
887 //    Class theType = first == null ? Object.class : first.getClass();
888 
889     // if last batch
890     if (batchIndex == numberOfBatches - 1) {
891 
892       // needs to work to 1-n
893       //int thisBatchSize = 1 + ((arraySize - 1) % batchSize);
894 
895       int collectionIndex = 0;
896       for (T t : collection) {
897         if (collectionIndex++ < batchIndex * batchSize) {
898           continue;
899         }
900         //just copy the rest
901         //if (collectionIndex >= (batchIndex * batchSize) + arraySize) {
902         //  break;
903         //}
904         //we are in the copy mode
905         theBatchObjects.add(t);
906       }
907 
908     } else {
909       // if non-last batch
910       //int newIndex = 0;
911       int collectionIndex = 0;
912       for (T t : collection) {
913         if (collectionIndex < batchIndex * batchSize) {
914           collectionIndex++;
915           continue;
916         }
917         //done with batch
918         if (collectionIndex >= (batchIndex + 1) * batchSize) {
919           break;
920         }
921         theBatchObjects.add(t);
922         collectionIndex++;
923       }
924     }
925     return theBatchObjects;
926   }
927   
928   /**
929    * split a string based on a separator into an array, and trim each entry (see
930    * the Commons Util trim() for more details)
931    * 
932    * @param input
933    *          is the delimited input to split and trim
934    * @param separator
935    *          is what to split on
936    * 
937    * @return the array of items after split and trimmed, or null if input is null.  will be trimmed to empty
938    */
939   public static String[] splitTrim(String input, String separator) {
940     return splitTrim(input, separator, true);
941   }
942 
943   /**
944    * split a string based on a separator into an array, and trim each entry (see
945    * the Commons Util trim() for more details)
946    * 
947    * @param input
948    *          is the delimited input to split and trim
949    * @param separator
950    *          is what to split on
951    * 
952    * @return the list of items after split and trimmed, or null if input is null.  will be trimmed to empty
953    */
954   public static List<String> splitTrimToList(String input, String separator) {
955     if (isBlank(input)) {
956       return null;
957     }
958     String[] array =  splitTrim(input, separator);
959     return toList(array);
960   }
961 
962   /**
963    * split a string based on a separator into an array, and trim each entry (see
964    * the Commons Util trim() for more details)
965    * 
966    * @param input
967    *          is the delimited input to split and trim
968    * @param separator
969    *          is what to split on
970    * @param treatAdjacentSeparatorsAsOne
971    * @return the array of items after split and trimmed, or null if input is null.  will be trimmed to empty
972    */
973   public static String[] splitTrim(String input, String separator, boolean treatAdjacentSeparatorsAsOne) {
974     if (isBlank(input)) {
975       return null;
976     }
977 
978     //first split
979     String[] items = treatAdjacentSeparatorsAsOne ? split(input, separator) : 
980       splitPreserveAllTokens(input, separator);
981 
982     //then trim
983     for (int i = 0; (items != null) && (i < items.length); i++) {
984       items[i] = trim(items[i]);
985     }
986 
987     //return the array
988     return items;
989   }
990 
991   /**
992    * escape url chars (e.g. a # is %23)
993    * @param string input
994    * @return the encoded string
995    */
996   public static String escapeUrlEncode(String string) {
997     String result = null;
998     try {
999       result = URLEncoder.encode(string, "UTF-8");
1000     } catch (UnsupportedEncodingException ex) {
1001       throw new RuntimeException("UTF-8 not supported", ex);
1002     }
1003     return result;
1004   }
1005   
1006   /**
1007    * unescape url chars (e.g. a space is %20)
1008    * @param string input
1009    * @return the encoded string
1010    */
1011   public static String escapeUrlDecode(String string) {
1012     String result = null;
1013     try {
1014       result = URLDecoder.decode(string, "UTF-8");
1015     } catch (UnsupportedEncodingException ex) {
1016       throw new RuntimeException("UTF-8 not supported", ex);
1017     }
1018     return result;
1019   }
1020 
1021   /**
1022    * make sure a list is non null.  If null, then return an empty list
1023    * @param <T>
1024    * @param list
1025    * @return the list or empty list if null
1026    */
1027   public static <T> List<T> nonNull(List<T> list) {
1028     return list == null ? new ArrayList<T>() : list;
1029   }
1030   
1031   /**
1032    * make sure a list is non null.  If null, then return an empty set
1033    * @param <T>
1034    * @param set
1035    * @return the set or empty set if null
1036    */
1037   public static <T> Set<T> nonNull(Set<T> set) {
1038     return set == null ? new HashSet<T>() : set;
1039   }
1040   
1041   /**
1042    * make sure it is non null, if null, then give new map
1043    * 
1044    * @param <K> key of map
1045    * @param <V> value of map
1046    * @param map is map
1047    * @return set non null
1048    */
1049   public static <K,V> Map<K,V> nonNull(Map<K,V> map) {
1050     return map == null ? new HashMap<K,V>() : map;
1051   }
1052 
1053   /**
1054    * return a list of objects from varargs.  Though if there is one
1055    * object, and it is a list, return it.
1056    * 
1057    * @param <T>
1058    *            template type of the objects
1059    * @param objects
1060    * @return the list or null if objects is null
1061    */
1062   @SuppressWarnings("unchecked")
1063   public static <T> List<T> toList(T... objects) {
1064     if (objects == null) {
1065       return null;
1066     }
1067     if (objects.length == 1 && objects[0] instanceof List) {
1068       return (List<T>)objects[0];
1069     }
1070     
1071     List<T> result = new ArrayList<T>();
1072     for (T object : objects) {
1073       result.add(object);
1074     }
1075     return result;
1076   }
1077 
1078   /**
1079    * convert classes to a list
1080    * @param classes
1081    * @return list of classes
1082    */
1083   public static List<Class<?>> toListClasses(Class<?>... classes) {
1084     return toList(classes);
1085   }
1086   
1087 
1088   
1089   /**
1090    * return a set of objects from varargs.
1091    * 
1092    * @param <T> template type of the objects
1093    * @param objects
1094    * @return the set
1095    */
1096   public static <T> Set<T> toSet(T... objects) {
1097 
1098     Set<T> result = new LinkedHashSet<T>();
1099     for (T object : objects) {
1100       result.add(object);
1101     }
1102     return result;
1103   }
1104 
1105   /**
1106    * cache separator
1107    */
1108   private static final String CACHE_SEPARATOR = "__";
1109 
1110   /**
1111    * string format of dates
1112    */
1113   public static final String DATE_FORMAT = "yyyyMMdd";
1114 
1115   /**
1116    * format including minutes and seconds: yyyy/MM/dd HH:mm:ss
1117    */
1118   public static final String DATE_MINUTES_SECONDS_FORMAT = "yyyy/MM/dd HH:mm:ss";
1119 
1120   /**
1121    * format including minutes and seconds: yyyyMMdd HH:mm:ss
1122    */
1123   public static final String DATE_MINUTES_SECONDS_NO_SLASH_FORMAT = "yyyyMMdd HH:mm:ss";
1124 
1125   /**
1126    * format on screen of config for milestone: yyyy/MM/dd HH:mm:ss.SSS
1127    */
1128   public static final String TIMESTAMP_FORMAT = "yyyy/MM/dd HH:mm:ss.SSS";
1129 
1130   /**
1131    * format on screen of config for milestone: yyyyMMdd HH:mm:ss.SSS
1132    */
1133   public static final String TIMESTAMP_NO_SLASH_FORMAT = "yyyyMMdd HH:mm:ss.SSS";
1134 
1135   /**
1136    * date format, make sure to synchronize
1137    */
1138   final static SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
1139 
1140   /**
1141    * synchronize code that uses this standard formatter for dates with minutes and seconds
1142    */
1143   final static SimpleDateFormat dateMinutesSecondsFormat = new SimpleDateFormat(
1144       DATE_MINUTES_SECONDS_FORMAT);
1145 
1146   /**
1147    * synchronize code that uses this standard formatter for dates with minutes and seconds
1148    */
1149   final static SimpleDateFormat dateMinutesSecondsNoSlashFormat = new SimpleDateFormat(
1150       DATE_MINUTES_SECONDS_NO_SLASH_FORMAT);
1151 
1152   /**
1153    * <pre> format: yyyy/MM/dd HH:mm:ss.SSS synchronize code that uses this standard formatter for timestamps </pre>
1154    */
1155   final static SimpleDateFormat timestampFormat = new SimpleDateFormat(TIMESTAMP_FORMAT);
1156 
1157   /**
1158    * synchronize code that uses this standard formatter for timestamps
1159    */
1160   final static SimpleDateFormat timestampNoSlashFormat = new SimpleDateFormat(
1161       TIMESTAMP_NO_SLASH_FORMAT);
1162 
1163   /**
1164    * If false, throw an assertException, and give a reason
1165    * 
1166    * @param isTrue
1167    * @param reason
1168    */
1169   public static void assertion(boolean isTrue, String reason) {
1170     if (!isTrue) {
1171       throw new RuntimeException(reason);
1172     }
1173 
1174   }
1175 
1176   /**
1177    * use the field cache, expire every day (just to be sure no leaks)
1178    */
1179   private static ExpirableCache<String, Set<Field>> fieldSetCache = null;
1180   
1181   /**
1182    * lazy load
1183    * @return field set cache
1184    */
1185   private static ExpirableCache<String, Set<Field>> fieldSetCache() {
1186     if (fieldSetCache == null) {
1187       fieldSetCache = new ExpirableCache<String, Set<Field>>(60*24);
1188     }
1189     return fieldSetCache;
1190   }
1191     
1192 
1193   /**
1194    * make a cache with max size to cache declared methods
1195    */
1196   private static ExpirableCache<Class, Method[]> declaredMethodsCache = null;
1197   
1198   /**
1199    * lazy load
1200    * @return declared method cache
1201    */
1202   private static ExpirableCache<Class, Method[]> declaredMethodsCache() {
1203     if (declaredMethodsCache == null) {
1204       declaredMethodsCache = new ExpirableCache<Class, Method[]>(60*24);
1205     }
1206     return declaredMethodsCache;
1207   }
1208   
1209     
1210 
1211   /**
1212    * use the field cache, expire every day (just to be sure no leaks) 
1213    */
1214   private static ExpirableCache<String, Set<Method>> getterSetCache = null;
1215     
1216 
1217   /**
1218    * lazy load
1219    * @return getter cache
1220    */
1221   private static ExpirableCache<String, Set<Method>> getterSetCache() {
1222     if (getterSetCache == null) {
1223       getterSetCache = new ExpirableCache<String, Set<Method>>(60*24);
1224     }
1225     return getterSetCache;
1226   }
1227   
1228     
1229 
1230   /**
1231    * use the field cache, expire every day (just to be sure no leaks) 
1232    */
1233   private static ExpirableCache<String, Set<Method>> setterSetCache = null;
1234     
1235 
1236   /**
1237    * lazy load
1238    * @return setter cache
1239    */
1240   private static ExpirableCache<String, Set<Method>> setterSetCache() {
1241     if (setterSetCache == null) {
1242       setterSetCache = new ExpirableCache<String, Set<Method>>(60*24);
1243     }
1244     return setterSetCache;
1245   }
1246   
1247   
1248   /**
1249    * Field lastId.
1250    */
1251   private static char[] lastId = convertLongToStringSmall(new Date().getTime())
1252       .toCharArray();
1253 
1254   /**
1255    * cache the properties read from resource 
1256    */
1257   private static Map<String, Properties> resourcePropertiesCache = new HashMap<String, Properties>();
1258 
1259   /**
1260    * assign data to a field
1261    * 
1262    * @param theClass
1263    *            the class which has the method
1264    * @param invokeOn
1265    *            to call on or null for static
1266    * @param fieldName
1267    *            method name to call
1268    * @param dataToAssign
1269    *            data
1270    * @param callOnSupers
1271    *            if static and method not exists, try on supers
1272    * @param overrideSecurity
1273    *            true to call on protected or private etc methods
1274    * @param typeCast
1275    *            true if we should typecast
1276    * @param annotationWithValueOverride
1277    *            annotation with value of override
1278    */
1279   public static void assignField(Class theClass, Object invokeOn,
1280       String fieldName, Object dataToAssign, boolean callOnSupers,
1281       boolean overrideSecurity, boolean typeCast,
1282       Class<? extends Annotation> annotationWithValueOverride) {
1283     if (theClass == null && invokeOn != null) {
1284       theClass = invokeOn.getClass();
1285     }
1286     Field field = field(theClass, fieldName, callOnSupers, true);
1287     assignField(field, invokeOn, dataToAssign, overrideSecurity, typeCast,
1288         annotationWithValueOverride);
1289   }
1290 
1291   /**
1292    * assign data to a field. Will find the field in superclasses, will
1293    * typecast, and will override security (private, protected, etc)
1294    * 
1295    * @param theClass
1296    *            the class which has the method
1297    * @param invokeOn
1298    *            to call on or null for static
1299    * @param fieldName
1300    *            method name to call
1301    * @param dataToAssign
1302    *            data
1303    * @param annotationWithValueOverride
1304    *            annotation with value of override
1305    */
1306   public static void assignField(Class theClass, Object invokeOn,
1307       String fieldName, Object dataToAssign,
1308       Class<? extends Annotation> annotationWithValueOverride) {
1309     assignField(theClass, invokeOn, fieldName, dataToAssign, true, true,
1310         true, annotationWithValueOverride);
1311   }
1312 
1313   /**
1314    * assign data to a field
1315    * 
1316    * @param field
1317    *            is the field to assign to
1318    * @param invokeOn
1319    *            to call on or null for static
1320    * @param dataToAssign
1321    *            data
1322    * @param overrideSecurity
1323    *            true to call on protected or private etc methods
1324    * @param typeCast
1325    *            true if we should typecast
1326    */
1327   @SuppressWarnings("unchecked")
1328   public static void assignField(Field field, Object invokeOn,
1329       Object dataToAssign, boolean overrideSecurity, boolean typeCast) {
1330 
1331     try {
1332       Class fieldType = field.getType();
1333       // typecast
1334       if (typeCast) {
1335         dataToAssign = 
1336                  typeCast(dataToAssign, fieldType,
1337                  true, true);
1338       }
1339       if (overrideSecurity) {
1340         field.setAccessible(true);
1341       }
1342       field.set(invokeOn, dataToAssign);
1343     } catch (Exception e) {
1344       throw new RuntimeException("Cant assign reflection field: "
1345           + (field == null ? null : field.getName()) + ", on: "
1346           + className(invokeOn) + ", with args: "
1347           + classNameCollection(dataToAssign), e);
1348     }
1349   }
1350 
1351   /**
1352    * null safe iterator getter if the type if collection
1353    * 
1354    * @param collection
1355    * @return the iterator
1356    */
1357   public static Iterator iterator(Object collection) {
1358     if (collection == null) {
1359       return null;
1360     }
1361     // array list doesnt need an iterator
1362     if (collection instanceof Collection
1363         && !(collection instanceof ArrayList)) {
1364       return ((Collection) collection).iterator();
1365     }
1366     return null;
1367   }
1368 
1369   /**
1370    * Null safe array length or map
1371    * 
1372    * @param arrayOrCollection
1373    * @return the length of the array (0 for null)
1374    */
1375   public static int length(Object arrayOrCollection) {
1376     if (arrayOrCollection == null) {
1377       return 0;
1378     }
1379     if (arrayOrCollection.getClass().isArray()) {
1380       return Array.getLength(arrayOrCollection);
1381     }
1382     if (arrayOrCollection instanceof Collection) {
1383       return ((Collection) arrayOrCollection).size();
1384     }
1385     if (arrayOrCollection instanceof Map) {
1386       return ((Map) arrayOrCollection).size();
1387     }
1388     // simple non array non collection object
1389     return 1;
1390   }
1391 
1392   /**
1393    * If array, get the element based on index, if Collection, get it based on
1394    * iterator.
1395    * 
1396    * @param arrayOrCollection
1397    * @param iterator
1398    * @param index
1399    * @return the object
1400    */
1401   public static Object next(Object arrayOrCollection, Iterator iterator,
1402       int index) {
1403     if (arrayOrCollection.getClass().isArray()) {
1404       return Array.get(arrayOrCollection, index);
1405     }
1406     if (arrayOrCollection instanceof ArrayList) {
1407       return ((ArrayList) arrayOrCollection).get(index);
1408     }
1409     if (arrayOrCollection instanceof Collection) {
1410       return iterator.next();
1411     }
1412     // simple object
1413     if (0 == index) {
1414       return arrayOrCollection;
1415     }
1416     throw new RuntimeException("Invalid class type: "
1417         + arrayOrCollection.getClass().getName());
1418   }
1419 
1420   /**
1421    * Remove the iterator or index
1422    * 
1423    * @param arrayOrCollection
1424    * @param index
1425    * @return the object list or new array
1426    */
1427   public static Object remove(Object arrayOrCollection, 
1428       int index) {
1429     return remove(arrayOrCollection, null, index);
1430   }
1431   
1432   /**
1433    * Remove the iterator or index
1434    * 
1435    * @param arrayOrCollection
1436    * @param iterator
1437    * @param index
1438    * @return the object list or new array
1439    */
1440   public static Object remove(Object arrayOrCollection, Iterator iterator,
1441       int index) {
1442     
1443     //if theres an iterator, just use that
1444     if (iterator != null) {
1445       iterator.remove();
1446       return arrayOrCollection;
1447     }
1448     if (arrayOrCollection.getClass().isArray()) {
1449       int newLength = Array.getLength(arrayOrCollection) - 1;
1450       Object newArray = Array.newInstance(arrayOrCollection.getClass().getComponentType(), newLength);
1451       if (newLength == 0) {
1452         return newArray;
1453       }
1454       if (index > 0) {
1455         System.arraycopy(arrayOrCollection, 0, newArray, 0, index);
1456       }
1457       if (index < newLength) {
1458         System.arraycopy(arrayOrCollection, index+1, newArray, index, newLength - index);
1459       }
1460       return newArray;
1461     }
1462     if (arrayOrCollection instanceof List) {
1463       ((List)arrayOrCollection).remove(index);
1464       return arrayOrCollection;
1465     } else if (arrayOrCollection instanceof Collection) {
1466       //this should work unless there are duplicates or something weird
1467       ((Collection)arrayOrCollection).remove(get(arrayOrCollection, index));
1468       return arrayOrCollection;
1469     }
1470     throw new RuntimeException("Invalid class type: "
1471         + arrayOrCollection.getClass().getName());
1472   }
1473 
1474   /**
1475    * print the simple names of a list of classes
1476    * @param object
1477    * @return the simple names
1478    */
1479   public static String classesString(Object object) {
1480     StringBuilder result = new StringBuilder();
1481     if (object.getClass().isArray()) {
1482       int length = Array.getLength(object);
1483       for (int i=0;i<length;i++) {
1484         result.append(((Class)object).getSimpleName());
1485         if (i < length-1) {
1486           result.append(", ");
1487         }
1488       }
1489       return result.toString();
1490     }
1491     
1492     throw new RuntimeException("Not implemented: " + className(object));
1493   }
1494   
1495   /**
1496    * null safe classname method, max out at 20
1497    * 
1498    * @param object
1499    * @return the classname
1500    */
1501   public static String classNameCollection(Object object) {
1502     if (object == null) {
1503       return null;
1504     }
1505     StringBuffer result = new StringBuffer();
1506     
1507     Iterator iterator = iterator(object);
1508     int length = length(object);
1509     for (int i = 0; i < length && i < 20; i++) {
1510       result.append(className(next(object, iterator, i)));
1511       if (i != length - 1) {
1512         result.append(", ");
1513       }
1514     }
1515     return result.toString();
1516   }
1517 
1518   /**
1519    * null safe classname method, gets the unenhanced name
1520    * 
1521    * @param object
1522    * @return the classname
1523    */
1524   public static String className(Object object) {
1525     return object == null ? null : object.getClass().getName();
1526   }
1527 
1528   /**
1529    * assign data to a field
1530    * 
1531    * @param field
1532    *            is the field to assign to
1533    * @param invokeOn
1534    *            to call on or null for static
1535    * @param dataToAssign
1536    *            data
1537    * @param overrideSecurity
1538    *            true to call on protected or private etc methods
1539    * @param typeCast
1540    *            true if we should typecast
1541    * @param annotationWithValueOverride
1542    *            annotation with value of override, or null if none
1543    */
1544   @SuppressWarnings("unchecked")
1545   public static void assignField(Field field, Object invokeOn,
1546       Object dataToAssign, boolean overrideSecurity, boolean typeCast,
1547       Class<? extends Annotation> annotationWithValueOverride) {
1548 
1549     if (annotationWithValueOverride != null) {
1550       // see if in annotation
1551       Annotation annotation = field
1552           .getAnnotation(annotationWithValueOverride);
1553       if (annotation != null) {
1554         
1555          // type of the value, or String if not specific Class
1556           // typeOfAnnotationValue = typeCast ? field.getType() :
1557           // String.class; dataToAssign =
1558           // AnnotationUtils.retrieveAnnotationValue(
1559           // typeOfAnnotationValue, annotation, "value");
1560         
1561         throw new RuntimeException("Not supported");
1562       }
1563     }
1564     assignField(field, invokeOn, dataToAssign, overrideSecurity, typeCast);
1565   }
1566 
1567   /**
1568    * assign data to a field. Will find the field in superclasses, will
1569    * typecast, and will override security (private, protected, etc)
1570    * 
1571    * @param invokeOn
1572    *            to call on or null for static
1573    * @param fieldName
1574    *            method name to call
1575    * @param dataToAssign
1576    *            data
1577    */
1578   public static void assignField(Object invokeOn, String fieldName,
1579       Object dataToAssign) {
1580     assignField(null, invokeOn, fieldName, dataToAssign, true, true, true,
1581         null);
1582   }
1583 
1584   /**
1585    * get a field object for a class, potentially in superclasses
1586    * 
1587    * @param theClass
1588    * @param fieldName
1589    * @param callOnSupers
1590    *            true if superclasses should be looked in for the field
1591    * @param throwExceptionIfNotFound
1592    *            will throw runtime exception if not found
1593    * @return the field object or null if not found (or exception if param is
1594    *         set)
1595    */
1596   public static Field field(Class theClass, String fieldName,
1597       boolean callOnSupers, boolean throwExceptionIfNotFound) {
1598     try {
1599       Field field = theClass.getDeclaredField(fieldName);
1600       // found it
1601       return field;
1602     } catch (NoSuchFieldException e) {
1603       // if method not found
1604       // if traversing up, and not Object, and not instance method
1605       if (callOnSupers && !theClass.equals(Object.class)) {
1606         return field(theClass.getSuperclass(), fieldName, callOnSupers,
1607             throwExceptionIfNotFound);
1608       }
1609     }
1610     // maybe throw an exception
1611     if (throwExceptionIfNotFound) {
1612       throw new RuntimeException("Cant find field: " + fieldName
1613           + ", in: " + theClass + ", callOnSupers: " + callOnSupers);
1614     }
1615     return null;
1616   }
1617 
1618   /**
1619    * return a set of Strings for a class and type. This is not for any
1620    * supertypes, only for the type at hand. includes final fields
1621    * 
1622    * @param theClass
1623    * @param fieldType
1624    *            or null for all
1625    * @param includeStaticFields
1626    * @return the set of strings, or the empty Set if none
1627    */
1628   @SuppressWarnings("unchecked")
1629   public static Set<String> fieldNames(Class theClass, Class fieldType,
1630       boolean includeStaticFields) {
1631     return fieldNamesHelper(theClass, theClass, fieldType, true, true,
1632         includeStaticFields, null, true);
1633   }
1634 
1635   /**
1636    * get all field names from a class, including superclasses (if specified)
1637    * 
1638    * @param theClass
1639    *            to look for fields in
1640    * @param superclassToStopAt
1641    *            to go up to or null to go up to Object
1642    * @param fieldType
1643    *            is the type of the field to get
1644    * @param includeSuperclassToStopAt
1645    *            if we should include the superclass
1646    * @param includeStaticFields
1647    *            if include static fields
1648    * @param includeFinalFields
1649    *            if final fields should be included
1650    * @return the set of field names or empty set if none
1651    */
1652   public static Set<String> fieldNames(Class theClass,
1653       Class superclassToStopAt, Class<?> fieldType,
1654       boolean includeSuperclassToStopAt, boolean includeStaticFields,
1655       boolean includeFinalFields) {
1656     return fieldNamesHelper(theClass, superclassToStopAt, fieldType,
1657         includeSuperclassToStopAt, includeStaticFields,
1658         includeFinalFields, null, true);
1659 
1660   }
1661 
1662   /**
1663    * get all field names from a class, including superclasses (if specified).
1664    * ignore a certain marker annotation
1665    * 
1666    * @param theClass
1667    *            to look for fields in
1668    * @param superclassToStopAt
1669    *            to go up to or null to go up to Object
1670    * @param fieldType
1671    *            is the type of the field to get
1672    * @param includeSuperclassToStopAt
1673    *            if we should include the superclass
1674    * @param includeStaticFields
1675    *            if include static fields
1676    * @param includeFinalFields
1677    *            if final fields should be included
1678    * @param markerAnnotationToIngore
1679    *            if this is not null, then if the field has this annotation,
1680    *            then do not include in list
1681    * @return the set of field names
1682    */
1683   public static Set<String> fieldNames(Class theClass,
1684       Class superclassToStopAt, Class<?> fieldType,
1685       boolean includeSuperclassToStopAt, boolean includeStaticFields,
1686       boolean includeFinalFields,
1687       Class<? extends Annotation> markerAnnotationToIngore) {
1688     return fieldNamesHelper(theClass, superclassToStopAt, fieldType,
1689         includeSuperclassToStopAt, includeStaticFields,
1690         includeFinalFields, markerAnnotationToIngore, false);
1691 
1692   }
1693 
1694   /**
1695    * get all field names from a class, including superclasses (if specified)
1696    * (up to and including the specified superclass). ignore a certain marker
1697    * annotation. Dont get static or final field, and get fields of all types
1698    * 
1699    * @param theClass
1700    *            to look for fields in
1701    * @param superclassToStopAt
1702    *            to go up to or null to go up to Object
1703    * @param markerAnnotationToIngore
1704    *            if this is not null, then if the field has this annotation,
1705    *            then do not include in list
1706    * @return the set of field names or empty set if none
1707    */
1708   public static Set<String> fieldNames(Class theClass,
1709       Class superclassToStopAt,
1710       Class<? extends Annotation> markerAnnotationToIngore) {
1711     return fieldNamesHelper(theClass, superclassToStopAt, null, true,
1712         false, false, markerAnnotationToIngore, false);
1713   }
1714 
1715   /**
1716    * get all field names from a class, including superclasses (if specified)
1717    * 
1718    * @param theClass
1719    *            to look for fields in
1720    * @param superclassToStopAt
1721    *            to go up to or null to go up to Object
1722    * @param fieldType
1723    *            is the type of the field to get
1724    * @param includeSuperclassToStopAt
1725    *            if we should include the superclass
1726    * @param includeStaticFields
1727    *            if include static fields
1728    * @param includeFinalFields
1729    *            true to include finals
1730    * @param markerAnnotation
1731    *            if this is not null, then if the field has this annotation,
1732    *            then do not include in list (if includeAnnotation is false)
1733    * @param includeAnnotation
1734    *            true if the attribute should be included if annotation is
1735    *            present, false if exclude
1736    * @return the set of field names or empty set if none
1737    */
1738   @SuppressWarnings("unchecked")
1739   static Set<String> fieldNamesHelper(Class theClass,
1740       Class superclassToStopAt, Class<?> fieldType,
1741       boolean includeSuperclassToStopAt, boolean includeStaticFields,
1742       boolean includeFinalFields,
1743       Class<? extends Annotation> markerAnnotation,
1744       boolean includeAnnotation) {
1745     Set<Field> fieldSet = fieldsHelper(theClass, superclassToStopAt,
1746         fieldType, includeSuperclassToStopAt, includeStaticFields,
1747         includeFinalFields, markerAnnotation, includeAnnotation);
1748     Set<String> fieldNameSet = new LinkedHashSet<String>();
1749     for (Field field : fieldSet) {
1750       fieldNameSet.add(field.getName());
1751     }
1752     return fieldNameSet;
1753 
1754   }
1755 
1756   /**
1757    * get all fields from a class, including superclasses (if specified)
1758    * 
1759    * @param theClass
1760    *            to look for fields in
1761    * @param superclassToStopAt
1762    *            to go up to or null to go up to Object
1763    * @param fieldType
1764    *            is the type of the field to get
1765    * @param includeSuperclassToStopAt
1766    *            if we should include the superclass
1767    * @param includeStaticFields
1768    *            if include static fields
1769    * @param includeFinalFields
1770    *            if final fields should be included
1771    * @param markerAnnotation
1772    *            if this is not null, then if the field has this annotation,
1773    *            then do not include in list (if includeAnnotation is false)
1774    * @param includeAnnotation
1775    *            true if the attribute should be included if annotation is
1776    *            present, false if exclude
1777    * @return the set of fields (wont return null)
1778    */
1779   @SuppressWarnings("unchecked")
1780   public static Set<Field> fields(Class theClass, Class superclassToStopAt,
1781       Class fieldType, boolean includeSuperclassToStopAt,
1782       boolean includeStaticFields, boolean includeFinalFields,
1783       Class<? extends Annotation> markerAnnotation,
1784       boolean includeAnnotation) {
1785     return fieldsHelper(theClass, superclassToStopAt, fieldType,
1786         includeSuperclassToStopAt, includeStaticFields,
1787         includeFinalFields, markerAnnotation, includeAnnotation);
1788   }
1789 
1790   /**
1791    * get all fields from a class, including superclasses (if specified) (up to
1792    * and including the specified superclass). ignore a certain marker
1793    * annotation, or only include it. Dont get static or final field, and get
1794    * fields of all types
1795    * 
1796    * @param theClass
1797    *            to look for fields in
1798    * @param superclassToStopAt
1799    *            to go up to or null to go up to Object
1800    * @param markerAnnotation
1801    *            if this is not null, then if the field has this annotation,
1802    *            then do not include in list (if includeAnnotation is false)
1803    * @param includeAnnotation
1804    *            true if the attribute should be included if annotation is
1805    *            present, false if exclude
1806    * @return the set of field names or empty set if none
1807    */
1808   @SuppressWarnings("unchecked")
1809   public static Set<Field> fields(Class theClass, Class superclassToStopAt,
1810       Class<? extends Annotation> markerAnnotation,
1811       boolean includeAnnotation) {
1812     return fieldsHelper(theClass, superclassToStopAt, null, true, false,
1813         false, markerAnnotation, includeAnnotation);
1814   }
1815 
1816   /**
1817    * get all fields from a class, including superclasses (if specified)
1818    * 
1819    * @param theClass
1820    *            to look for fields in
1821    * @param superclassToStopAt
1822    *            to go up to or null to go up to Object
1823    * @param fieldType
1824    *            is the type of the field to get
1825    * @param includeSuperclassToStopAt
1826    *            if we should include the superclass
1827    * @param includeStaticFields
1828    *            if include static fields
1829    * @param includeFinalFields
1830    *            if final fields should be included
1831    * @param markerAnnotation
1832    *            if this is not null, then if the field has this annotation,
1833    *            then do not include in list (if includeAnnotation is false)
1834    * @param includeAnnotation
1835    *            true if the attribute should be included if annotation is
1836    *            present, false if exclude
1837    * @return the set of fields (wont return null)
1838    */
1839   @SuppressWarnings("unchecked")
1840   static Set<Field> fieldsHelper(Class theClass, Class superclassToStopAt,
1841       Class<?> fieldType, boolean includeSuperclassToStopAt,
1842       boolean includeStaticFields, boolean includeFinalFields,
1843       Class<? extends Annotation> markerAnnotation,
1844       boolean includeAnnotation) {
1845     // MAKE SURE IF ANY MORE PARAMS ARE ADDED, THE CACHE KEY IS CHANGED!
1846 
1847     Set<Field> fieldNameSet = null;
1848     String cacheKey = theClass + CACHE_SEPARATOR + superclassToStopAt
1849         + CACHE_SEPARATOR + fieldType + CACHE_SEPARATOR
1850         + includeSuperclassToStopAt + CACHE_SEPARATOR
1851         + includeStaticFields + CACHE_SEPARATOR + includeFinalFields
1852         + CACHE_SEPARATOR + markerAnnotation + CACHE_SEPARATOR
1853         + includeAnnotation;
1854     fieldNameSet = fieldSetCache().get(cacheKey);
1855     if (fieldNameSet != null) {
1856       return fieldNameSet;
1857     }
1858 
1859     fieldNameSet = new LinkedHashSet<Field>();
1860     fieldsHelper(theClass, superclassToStopAt, fieldType,
1861         includeSuperclassToStopAt, includeStaticFields,
1862         includeFinalFields, markerAnnotation, fieldNameSet,
1863         includeAnnotation);
1864 
1865     // add to cache
1866     fieldSetCache().put(cacheKey, fieldNameSet);
1867 
1868     return fieldNameSet;
1869 
1870   }
1871 
1872   /**
1873    * compare two objects, compare primitives, Strings, maps of string attributes.
1874    * if both objects equal each others references, then return empty set.
1875    * then, if not, then if either is null, return all fields
1876    * @param first
1877    * @param second
1878    * @param fieldsToCompare
1879    * @param mapPrefix is the prefix for maps which are compared (e.g. attribute__)
1880    * @return the set of fields which are different.  never returns null
1881    */
1882   @SuppressWarnings("unchecked")
1883   public static Set<String> compareObjectFields(Object first, Object second, 
1884       Set<String> fieldsToCompare, String mapPrefix) {
1885     
1886     Set<String> differentFields = new LinkedHashSet<String>();
1887     
1888     if (first == second) {
1889       return differentFields;
1890     }
1891     
1892     //if either null, then all fields are different
1893     if (first == null || second == null) {
1894       differentFields.addAll(fieldsToCompare);
1895     }
1896 
1897     for (String fieldName : fieldsToCompare) {
1898       try {
1899         Object firstValue = fieldValue(first, fieldName);
1900         Object secondValue = fieldValue(second, fieldName);
1901         
1902         if (firstValue == secondValue) {
1903           continue;
1904         }
1905         if (firstValue instanceof Map || secondValue instanceof Map) {
1906           mapDifferences((Map)firstValue, (Map)secondValue, differentFields, mapPrefix);
1907           continue;
1908         }
1909         //compare things...
1910         //for strings, null is equal to empty
1911         if (firstValue instanceof String || secondValue instanceof String) {
1912           if (!equals(defaultString((String)firstValue),
1913               defaultString((String)secondValue))) {
1914             differentFields.add(fieldName);
1915           }
1916           continue;
1917         }
1918         //if one is null, that is not good
1919         if (firstValue == null || secondValue == null) {
1920           differentFields.add(fieldName);
1921           continue;
1922         }
1923         //everything (numbers, dates, etc) should work with equals method...
1924         if (!firstValue.equals(secondValue)) {
1925           differentFields.add(fieldName);
1926           continue;
1927         }
1928         
1929       } catch (RuntimeException re) {
1930         throw new RuntimeException("Problem comparing field " + fieldName 
1931             + " on objects: " + className(first) + ", " + className(second));
1932       }
1933       
1934       
1935     }
1936     return differentFields;
1937   }
1938   
1939   /**
1940    * clone an object, assign primitives, Strings, maps of string attributes.  Clone GrouperCloneable fields.
1941    * @param <T> template
1942    * @param object
1943    * @param fieldsToClone
1944    * @return the cloned object or null if input is null
1945    */
1946   @SuppressWarnings("unchecked")
1947   public static <T> T clone(T object, Set<String> fieldsToClone) {
1948     
1949     //make a return object
1950     T result = (T)newInstance(object.getClass());
1951     
1952     cloneFields(object, result, fieldsToClone);
1953     
1954     return result;
1955   }
1956   
1957   /**
1958    * clone an object, assign primitives, Strings, maps of string attributes.  Clone GrouperCloneable fields.
1959    * @param <T> template
1960    * @param object
1961    * @param result 
1962    * @param fieldsToClone
1963    */
1964   public static <T> void cloneFields(T object, T result,
1965       Set<String> fieldsToClone) {
1966     
1967     if (object == result) {
1968       return;
1969     }
1970     
1971     //if either null, then all fields are different
1972     if (object == null || result == null) {
1973       throw new RuntimeException("Cant copy from or to null: " + className(object) + ", " + className(result));
1974     }
1975     
1976     Class<?> fieldValueClass = null;
1977     
1978     for (String fieldName : nonNull(fieldsToClone)) {
1979       try {
1980         
1981         Object fieldValue = fieldValue(object, fieldName);
1982         fieldValueClass = fieldValue == null ? null : fieldValue.getClass();
1983         
1984         Object fieldValueToAssign = cloneValue(fieldValue);
1985         
1986         //assign the field to the clone
1987         assignField(result, fieldName, fieldValueToAssign);
1988         
1989       } catch (RuntimeException re) {
1990         throw new RuntimeException("Problem cloning field: " + object.getClass() 
1991               + ", " + fieldName + ", " + fieldValueClass, re);
1992       }
1993     }
1994   }
1995   
1996   /**
1997    * helper method to clone the value of a field.  just returns the same
1998    * reference for primitives and immutables.  Will subclone GrouperCloneables, 
1999    * and will throw exception if not expecting the type.  Will clone sets, lists, maps.
2000    * @param <T> template
2001    * 
2002    * @param value
2003    * @return the cloned value
2004    */
2005   @SuppressWarnings("unchecked")
2006   public static <T> T cloneValue(T value) {
2007 
2008     Object clonedValue = value;
2009     
2010     if (value == null || value instanceof String 
2011         || value.getClass().isPrimitive() || value instanceof Number
2012         || value instanceof Boolean
2013         || value instanceof Date) {
2014       //clone things
2015       //for strings, and immutable classes, just assign
2016       //nothing to do, just assign the value
2017     } else if (value instanceof Map) {
2018       clonedValue = new LinkedHashMap();
2019       Map mapValue = (Map)value;
2020       Map clonedMapValue = (Map)clonedValue;
2021       for (Object key : mapValue.keySet()) {
2022         clonedMapValue.put(cloneValue(key), cloneValue(mapValue.get(key)));
2023       }
2024     } else if (value instanceof Set) {
2025         clonedValue = new LinkedHashSet();
2026         Set setValue = (Set)value;
2027         Set clonedSetValue = (Set)clonedValue;
2028         for (Object each : setValue) {
2029           clonedSetValue.add(cloneValue(each));
2030         }
2031     } else if (value instanceof List) {
2032       clonedValue = new ArrayList();
2033       List listValue = (List)value;
2034       List clonedListValue = (List)clonedValue;
2035       for (Object each : listValue) {
2036         clonedListValue.add(cloneValue(each));
2037       }
2038     } else if (value.getClass().isArray()) {
2039       clonedValue = Array.newInstance(value.getClass().getComponentType(), Array.getLength(value));
2040       for (int i=0;i<Array.getLength(value);i++) {
2041         Array.set(clonedValue, i, cloneValue(Array.get(value, i)));
2042       }
2043       
2044       
2045     } else {
2046 
2047       //this means lets add support for a new type of object
2048       throw new RuntimeException("Unexpected class in clone method: " + value.getClass());
2049     
2050     }
2051     return (T)clonedValue;
2052   }
2053   
2054   /**
2055    * simple method to get method names
2056    * @param theClass
2057    * @param superclassToStopAt 
2058    * @param includeSuperclassToStopAt 
2059    * @param includeStaticMethods 
2060    * @return the set of method names
2061    */
2062   public static Set<String> methodNames(Class<?> theClass, Class<?> superclassToStopAt, 
2063       boolean includeSuperclassToStopAt, boolean includeStaticMethods) {
2064 
2065     Set<Method> methods = new LinkedHashSet<Method>();
2066     methodsHelper(theClass, superclassToStopAt, includeSuperclassToStopAt, includeStaticMethods, 
2067         null, false, methods);
2068     Set<String> methodNames = new HashSet<String>();
2069     for (Method method : methods) {
2070       methodNames.add(method.getName());
2071     }
2072     return methodNames;
2073   }
2074 
2075   /**
2076    * get the set of methods
2077    * @param theClass
2078    * @param superclassToStopAt 
2079    * @param includeSuperclassToStopAt 
2080    * @param includeStaticMethods
2081    * @param markerAnnotation 
2082    * @param includeAnnotation 
2083    * @param methodSet
2084    */
2085   public static void methodsHelper(Class<?> theClass, Class<?> superclassToStopAt, 
2086       boolean includeSuperclassToStopAt,
2087       boolean includeStaticMethods, Class<? extends Annotation> markerAnnotation, 
2088       boolean includeAnnotation, Set<Method> methodSet) {
2089     Method[] methods = theClass.getDeclaredMethods();
2090     if (length(methods) != 0) {
2091       for (Method method : methods) {
2092         // if not static, then continue
2093         if (!includeStaticMethods
2094             && Modifier.isStatic(method.getModifiers())) {
2095           continue;
2096         }
2097         // if checking for annotation
2098         if (markerAnnotation != null
2099             && (includeAnnotation != method
2100                 .isAnnotationPresent(markerAnnotation))) {
2101           continue;
2102         }
2103         // go for it
2104         methodSet.add(method);
2105       }
2106     }
2107     // see if done recursing (if superclassToStopAt is null, then stop at
2108     // Object
2109     if (theClass.equals(superclassToStopAt)
2110         || theClass.equals(Object.class)) {
2111       return;
2112     }
2113     Class superclass = theClass.getSuperclass();
2114     if (!includeSuperclassToStopAt && superclass.equals(superclassToStopAt)) {
2115       return;
2116     }
2117     // recurse
2118     methodsHelper(superclass, superclassToStopAt,
2119         includeSuperclassToStopAt, includeStaticMethods,
2120         markerAnnotation, includeAnnotation, methodSet);
2121     
2122   }
2123   
2124   /**
2125    * get the set of methods
2126    * @param theClass
2127    * @param methodName 
2128    * @param paramTypesOrArrayOrList
2129    *            types of the params
2130    * @param superclassToStopAt 
2131    * @param includeSuperclassToStopAt 
2132    * @param isStaticOrInstance true if static
2133    * @param markerAnnotation 
2134    * @param includeAnnotation 
2135    * @return the method or null if not found
2136    *            
2137    */
2138   public static Method method(Class<?> theClass, 
2139       String methodName, Object paramTypesOrArrayOrList,
2140       Class<?> superclassToStopAt, 
2141       boolean includeSuperclassToStopAt,
2142       boolean isStaticOrInstance, Class<? extends Annotation> markerAnnotation, 
2143       boolean includeAnnotation) {
2144 
2145     Class[] paramTypesArray = (Class[]) toArray(paramTypesOrArrayOrList);
2146 
2147     Method method = null;
2148     
2149     try {
2150       method = theClass.getDeclaredMethod(methodName, paramTypesArray);
2151     } catch (NoSuchMethodException nsme) {
2152       //this is ok
2153     } catch (Exception e) {
2154       throw new RuntimeException("Problem retrieving method: " + theClass.getSimpleName() + ", " + methodName, e);
2155     }
2156     
2157     if (method != null) {
2158       //we found a method, make sure it is valid
2159       // if not static, then return null (dont worry about superclass)
2160       if (!isStaticOrInstance
2161           && Modifier.isStatic(method.getModifiers())) {
2162         return null;
2163       }
2164       // if checking for annotation, if not there, then recurse
2165       if (markerAnnotation == null
2166           || (includeAnnotation == method
2167               .isAnnotationPresent(markerAnnotation))) {
2168         return method;
2169       }
2170     }
2171     // see if done recursing (if superclassToStopAt is null, then stop at
2172     // Object
2173     if (theClass.equals(superclassToStopAt)
2174         || theClass.equals(Object.class)) {
2175       return null;
2176     }
2177     Class superclass = theClass.getSuperclass();
2178     if (!includeSuperclassToStopAt && superclass.equals(superclassToStopAt)) {
2179       return null;
2180     }
2181     // recurse
2182     return method(superclass, methodName, paramTypesArray, superclassToStopAt,
2183         includeSuperclassToStopAt, isStaticOrInstance, markerAnnotation, includeAnnotation);
2184   }
2185   
2186   /**
2187    * get all field names from a class, including superclasses (if specified)
2188    * 
2189    * @param theClass
2190    *            to look for fields in
2191    * @param superclassToStopAt
2192    *            to go up to or null to go up to Object
2193    * @param fieldType
2194    *            is the type of the field to get
2195    * @param includeSuperclassToStopAt
2196    *            if we should include the superclass
2197    * @param includeStaticFields
2198    *            if include static fields
2199    * @param includeFinalFields
2200    *            if final fields should be included
2201    * @param markerAnnotation
2202    *            if this is not null, then if the field has this annotation,
2203    *            then do not include in list
2204    * @param fieldSet
2205    *            set to add fields to
2206    * @param includeAnnotation
2207    *            if include or exclude
2208    */
2209   @SuppressWarnings("unchecked")
2210   private static void fieldsHelper(Class theClass, Class superclassToStopAt,
2211       Class<?> fieldType, boolean includeSuperclassToStopAt,
2212       boolean includeStaticFields, boolean includeFinalFields,
2213       Class<? extends Annotation> markerAnnotation, Set<Field> fieldSet,
2214       boolean includeAnnotation) {
2215     Field[] fields = theClass.getDeclaredFields();
2216     if (length(fields) != 0) {
2217       for (Field field : fields) {
2218         // if checking for type, and not right type, continue
2219         if (fieldType != null
2220             && !fieldType.isAssignableFrom(field.getType())) {
2221           continue;
2222         }
2223         // if not static, then continue
2224         if (!includeStaticFields
2225             && Modifier.isStatic(field.getModifiers())) {
2226           continue;
2227         }
2228         // if not final constinue
2229         if (!includeFinalFields
2230             && Modifier.isFinal(field.getModifiers())) {
2231           continue;
2232         }
2233         // if checking for annotation
2234         if (markerAnnotation != null
2235             && (includeAnnotation != field
2236                 .isAnnotationPresent(markerAnnotation))) {
2237           continue;
2238         }
2239         // go for it
2240         fieldSet.add(field);
2241       }
2242     }
2243     // see if done recursing (if superclassToStopAt is null, then stop at
2244     // Object
2245     if (theClass.equals(superclassToStopAt)
2246         || theClass.equals(Object.class)) {
2247       return;
2248     }
2249     Class superclass = theClass.getSuperclass();
2250     if (!includeSuperclassToStopAt && superclass.equals(superclassToStopAt)) {
2251       return;
2252     }
2253     // recurse
2254     fieldsHelper(superclass, superclassToStopAt, fieldType,
2255         includeSuperclassToStopAt, includeStaticFields,
2256         includeFinalFields, markerAnnotation, fieldSet,
2257         includeAnnotation);
2258   }
2259 
2260   /**
2261    * find out a field value
2262    * 
2263    * @param theClass
2264    *            the class which has the method
2265    * @param invokeOn
2266    *            to call on or null for static
2267    * @param fieldName
2268    *            method name to call
2269    * @param callOnSupers
2270    *            if static and method not exists, try on supers
2271    * @param overrideSecurity
2272    *            true to call on protected or private etc methods
2273    * @return the current value
2274    */
2275   public static Object fieldValue(Class theClass, Object invokeOn,
2276       String fieldName, boolean callOnSupers, boolean overrideSecurity) {
2277     Field field = null;
2278 
2279     // only if the method exists, try to execute
2280     try {
2281       // ok if null
2282       if (theClass == null) {
2283         theClass = invokeOn.getClass();
2284       }
2285       field = field(theClass, fieldName, callOnSupers, true);
2286       return fieldValue(field, invokeOn, overrideSecurity);
2287     } catch (Exception e) {
2288       throw new RuntimeException("Cant execute reflection field: "
2289           + fieldName + ", on: " + className(invokeOn), e);
2290     }
2291   }
2292 
2293   /**
2294    * get the value of a field, override security if needbe
2295    * 
2296    * @param field
2297    * @param invokeOn
2298    * @return the value of the field
2299    */
2300   public static Object fieldValue(Field field, Object invokeOn) {
2301     return fieldValue(field, invokeOn, true);
2302   }
2303 
2304   /**
2305    * get the value of a field
2306    * 
2307    * @param field
2308    * @param invokeOn
2309    * @param overrideSecurity
2310    * @return the value of the field
2311    */
2312   public static Object fieldValue(Field field, Object invokeOn,
2313       boolean overrideSecurity) {
2314     if (overrideSecurity) {
2315       field.setAccessible(true);
2316     }
2317     try {
2318       return field.get(invokeOn);
2319     } catch (Exception e) {
2320       throw new RuntimeException("Cant execute reflection field: "
2321           + field.getName() + ", on: " + className(invokeOn), e);
2322 
2323     }
2324 
2325   }
2326 
2327   /**
2328    * find out a field value (invoke on supers, override security)
2329    * 
2330    * @param invokeOn
2331    *            to call on or null for static
2332    * @param fieldName
2333    *            method name to call
2334    * @return the current value
2335    */
2336   public static Object fieldValue(Object invokeOn, String fieldName) {
2337     return fieldValue(null, invokeOn, fieldName, true, true);
2338   }
2339 
2340   /**
2341    * get the decalred methods for a class, perhaps from cache
2342    * 
2343    * @param theClass
2344    * @return the declared methods
2345    */
2346   @SuppressWarnings("unused")
2347   private static Method[] retrieveDeclaredMethods(Class theClass) {
2348     Method[] methods = declaredMethodsCache().get(theClass);
2349     // get from cache if we can
2350     if (methods == null) {
2351       methods = theClass.getDeclaredMethods();
2352       declaredMethodsCache().put(theClass, methods);
2353     }
2354     return methods;
2355   }
2356 
2357   /**
2358    * helper method for calling a method with no params (could be in
2359    * superclass)
2360    * 
2361    * @param theClass
2362    *            the class which has the method
2363    * @param invokeOn
2364    *            to call on or null for static
2365    * @param methodName
2366    *            method name to call
2367    * @return the data
2368    */
2369   public static Object callMethod(Class theClass, Object invokeOn,
2370       String methodName) {
2371     return callMethod(theClass, invokeOn, methodName, null, null);
2372   }
2373 
2374   /**
2375    * helper method for calling a method (could be in superclass)
2376    * 
2377    * @param theClass
2378    *            the class which has the method
2379    * @param invokeOn
2380    *            to call on or null for static
2381    * @param methodName
2382    *            method name to call
2383    * @param paramTypesOrArrayOrList
2384    *            types of the params
2385    * @param paramsOrListOrArray
2386    *            data
2387    * @return the data
2388    */
2389   public static Object callMethod(Class theClass, Object invokeOn,
2390       String methodName, Object paramTypesOrArrayOrList,
2391       Object paramsOrListOrArray) {
2392     return callMethod(theClass, invokeOn, methodName,
2393         paramTypesOrArrayOrList, paramsOrListOrArray, true);
2394   }
2395 
2396   /**
2397    * helper method for calling a method
2398    * 
2399    * @param theClass
2400    *            the class which has the method
2401    * @param invokeOn
2402    *            to call on or null for static
2403    * @param methodName
2404    *            method name to call
2405    * @param paramTypesOrArrayOrList
2406    *            types of the params
2407    * @param paramsOrListOrArray
2408    *            data
2409    * @param callOnSupers
2410    *            if static and method not exists, try on supers
2411    * @return the data
2412    */
2413   public static Object callMethod(Class theClass, Object invokeOn,
2414       String methodName, Object paramTypesOrArrayOrList,
2415       Object paramsOrListOrArray, boolean callOnSupers) {
2416     return callMethod(theClass, invokeOn, methodName,
2417         paramTypesOrArrayOrList, paramsOrListOrArray, callOnSupers,
2418         false);
2419   }
2420 
2421   /**
2422    * construct an instance by reflection
2423    * @param <T>
2424    * @param theClass
2425    * @param args
2426    * @param types
2427    * @return the instance
2428    */
2429   public static <T> T construct(Class<T> theClass, Class[] types, Object[] args) {
2430     try {
2431       Constructor<T> constructor = theClass.getConstructor(types);
2432       
2433       return constructor.newInstance(args);
2434       
2435     } catch (Exception e) {
2436       throw new RuntimeException("Having trouble with constructor for class: " + theClass.getSimpleName()
2437           + " and args: " + classesString(types), e);
2438      }
2439   }
2440   
2441   /**
2442    * helper method for calling a method
2443    * 
2444    * @param theClass
2445    *            the class which has the method
2446    * @param invokeOn
2447    *            to call on or null for static
2448    * @param methodName
2449    *            method name to call
2450    * @param paramTypesOrArrayOrList
2451    *            types of the params
2452    * @param paramsOrListOrArray
2453    *            data
2454    * @param callOnSupers
2455    *            if static and method not exists, try on supers
2456    * @param overrideSecurity
2457    *            true to call on protected or private etc methods
2458    * @return the data
2459    */
2460   public static Object callMethod(Class theClass, Object invokeOn,
2461       String methodName, Object paramTypesOrArrayOrList,
2462       Object paramsOrListOrArray, boolean callOnSupers,
2463       boolean overrideSecurity) {
2464     Method method = null;
2465 
2466     Class[] paramTypesArray = (Class[]) toArray(paramTypesOrArrayOrList);
2467 
2468     try {
2469       method = theClass.getDeclaredMethod(methodName, paramTypesArray);
2470       if (overrideSecurity) {
2471         method.setAccessible(true);
2472       }
2473     } catch (Exception e) {
2474       // if method not found
2475       if (e instanceof NoSuchMethodException) {
2476         // if traversing up, and not Object, and not instance method
2477         // CH 070425 not sure why invokeOn needs to be null, removing
2478         // this
2479         if (callOnSupers /* && invokeOn == null */
2480             && !theClass.equals(Object.class)) {
2481           return callMethod(theClass.getSuperclass(), invokeOn,
2482               methodName, paramTypesOrArrayOrList,
2483               paramsOrListOrArray, callOnSupers, overrideSecurity);
2484         }
2485       }
2486       throw new RuntimeException("Problem calling method " + methodName
2487           + " on " + theClass.getName(), e);
2488     }
2489 
2490     return invokeMethod(method, invokeOn, paramsOrListOrArray);
2491 
2492   }
2493   
2494   /** pass this in the invokeOn to signify no params */
2495   private static final Object NO_PARAMS = new Object();
2496   
2497   /**
2498    * Safely invoke a reflection method that takes no args
2499    * 
2500    * @param method
2501    *            to invoke
2502    * @param invokeOn
2503    * if NO_PARAMS then will not pass in params.
2504    * @return the result
2505    */
2506   public static Object invokeMethod(Method method, Object invokeOn) {
2507     return invokeMethod(method, invokeOn, NO_PARAMS);
2508   }
2509 
2510   /**
2511    * Safely invoke a reflection method
2512    * 
2513    * @param method
2514    *            to invoke
2515    * @param invokeOn
2516    * @param paramsOrListOrArray must be an arg.  If null, will pass null.
2517    * if NO_PARAMS then will not pass in params.
2518    * @return the result
2519    */
2520   public static Object invokeMethod(Method method, Object invokeOn,
2521       Object paramsOrListOrArray) {
2522 
2523     Object[] args = paramsOrListOrArray == NO_PARAMS ? null : (Object[]) toArray(paramsOrListOrArray);
2524 
2525     //we want to make sure things are accessible
2526     method.setAccessible(true);
2527 
2528     //only if the method exists, try to execute
2529     Object result = null;
2530     Exception e = null;
2531     try {
2532       result = method.invoke(invokeOn, args);
2533     } catch (IllegalAccessException iae) {
2534       e = iae;
2535     } catch (IllegalArgumentException iae) {
2536       e = iae;
2537     } catch (InvocationTargetException ite) {
2538       //this means the underlying call caused exception... its ok if runtime
2539       if (ite.getCause() instanceof RuntimeException) {
2540         throw (RuntimeException)ite.getCause();
2541       }
2542       //else throw as invocation target...
2543       e = ite;
2544     }
2545     if (e != null) {
2546       throw new RuntimeException("Cant execute reflection method: "
2547           + method.getName() + ", on: " + className(invokeOn)
2548           + ", with args: " + classNameCollection(args), e);
2549     }
2550     return result;
2551   }
2552 
2553   /**
2554    * Convert a list to an array with the type of the first element e.g. if it
2555    * is a list of Person objects, then the array is Person[]
2556    * 
2557    * @param objectOrArrayOrCollection
2558    *            is a list
2559    * @return the array of objects with type of the first element in the list
2560    */
2561   public static Object toArray(Object objectOrArrayOrCollection) {
2562     // do this before length since if array with null in it, we want ti get
2563     // it back
2564     if (objectOrArrayOrCollection != null
2565         && objectOrArrayOrCollection.getClass().isArray()) {
2566       return objectOrArrayOrCollection;
2567     }
2568     int length = length(objectOrArrayOrCollection);
2569     if (length == 0) {
2570       return null;
2571     }
2572 
2573     if (objectOrArrayOrCollection instanceof Collection) {
2574       Collection collection = (Collection) objectOrArrayOrCollection;
2575       Object first = collection.iterator().next();
2576       return toArray(collection, (Class<?>)(first == null ? Object.class : first
2577           .getClass()));
2578     }
2579     // make an array of the type of object passed in, size one
2580     Object array = Array.newInstance(objectOrArrayOrCollection.getClass(),
2581         1);
2582     Array.set(array, 0, objectOrArrayOrCollection);
2583     return array;
2584   }
2585 
2586   /**
2587    * convert a list into an array of type of theClass
2588    * @param <T> is the type of the array
2589    * @param collection list to convert
2590    * @param theClass type of array to return
2591    * @return array of type theClass[] filled with the objects from list
2592    */
2593   @SuppressWarnings("unchecked")
2594   public static <T> T[] toArray(Collection collection, Class<T> theClass) {
2595     if (collection == null || collection.size() == 0) {
2596       return null;
2597     }
2598 
2599     return (T[])collection.toArray((Object[]) Array.newInstance(theClass,
2600         collection.size()));
2601 
2602   }
2603 
2604   /**
2605    * helper method for calling a static method up the stack. method takes no
2606    * args (could be in superclass)
2607    * 
2608    * @param theClass
2609    *            the class which has the method
2610    * @param methodName
2611    *            method name to call
2612    * @return the data
2613    */
2614   public static Object callMethod(Class theClass, String methodName) {
2615     return callMethod(theClass, null, methodName, null, null);
2616   }
2617 
2618   /**
2619    * helper method for calling a static method with no params
2620    * 
2621    * @param theClass
2622    *            the class which has the method
2623    * @param methodName
2624    *            method name to call
2625    * @param callOnSupers
2626    *            if we should try the super classes if not exists in this class
2627    * @return the data
2628    */
2629   public static Object callMethod(Class theClass, String methodName,
2630       boolean callOnSupers) {
2631     return callMethod(theClass, null, methodName, null, null, callOnSupers);
2632   }
2633 
2634   /**
2635    * helper method for calling a static method up the stack
2636    * 
2637    * @param theClass
2638    *            the class which has the method
2639    * @param methodName
2640    *            method name to call
2641    * @param paramTypesOrArrayOrList
2642    *            types of the params
2643    * @param paramsOrListOrArray
2644    *            data
2645    * @return the data
2646    */
2647   public static Object callMethod(Class theClass, String methodName,
2648       Object paramTypesOrArrayOrList, Object paramsOrListOrArray) {
2649     return callMethod(theClass, null, methodName, paramTypesOrArrayOrList,
2650         paramsOrListOrArray);
2651   }
2652 
2653   /**
2654    * helper method for calling a method with no params (could be in
2655    * superclass), will override security
2656    * 
2657    * @param invokeOn
2658    *            instance to invoke on
2659    * @param methodName
2660    *            method name to call not exists in this class
2661    * @return the data
2662    */
2663   public static Object callMethod(Object invokeOn, String methodName) {
2664     if (invokeOn == null) {
2665       throw new NullPointerException("invokeOn is null: " + methodName);
2666     }
2667     return callMethod(invokeOn.getClass(), invokeOn, methodName, null,
2668         null, true, true);
2669   }
2670 
2671   /**
2672    * replace a string or strings from a string, and put the output in a string
2673    * buffer. This does not recurse
2674    * 
2675    * @param text
2676    *            string to look in
2677    * @param searchFor
2678    *            string array to search for
2679    * @param replaceWith
2680    *            string array to replace with
2681    * @return the string
2682    */
2683   public static String replace(String text, Object searchFor,
2684       Object replaceWith) {
2685     return replace(null, null, text, searchFor, replaceWith, false, 0,
2686         false);
2687   }
2688 
2689   /**
2690    * replace a string or strings from a string, and put the output in a string
2691    * buffer
2692    * 
2693    * @param text
2694    *            string to look in
2695    * @param searchFor
2696    *            string array to search for
2697    * @param replaceWith
2698    *            string array to replace with
2699    * @param recurse
2700    *            if true then do multiple replaces (on the replacements)
2701    * @return the string
2702    */
2703   public static String replace(String text, Object searchFor,
2704       Object replaceWith, boolean recurse) {
2705     return replace(null, null, text, searchFor, replaceWith, recurse,
2706         recurse ? length(searchFor) : 0, false);
2707   }
2708 
2709   /**
2710    * replace a string or strings from a string, and put the output in a string
2711    * buffer
2712    * 
2713    * @param text
2714    *            string to look in
2715    * @param searchFor
2716    *            string array to search for
2717    * @param replaceWith
2718    *            string array to replace with
2719    * @param recurse
2720    *            if true then do multiple replaces (on the replacements)
2721    * @param removeIfFound
2722    *            true if removing from searchFor and replaceWith if found
2723    * @return the string
2724    */
2725   public static String replace(String text, Object searchFor,
2726       Object replaceWith, boolean recurse, boolean removeIfFound) {
2727     return replace(null, null, text, searchFor, replaceWith, recurse,
2728         recurse ? length(searchFor) : 0, removeIfFound);
2729   }
2730 
2731   /**
2732    * <p>
2733    * Replaces all occurrences of a String within another String.
2734    * </p>
2735    * 
2736    * <p>
2737    * A <code>null</code> reference passed to this method is a no-op.
2738    * </p>
2739    * 
2740    * <pre>
2741    * replace(null, *, *)        = null
2742    * replace(&quot;&quot;, *, *)          = &quot;&quot;
2743    * replace(&quot;any&quot;, null, *)    = &quot;any&quot;
2744    * replace(&quot;any&quot;, *, null)    = &quot;any&quot;
2745    * replace(&quot;any&quot;, &quot;&quot;, *)      = &quot;any&quot;
2746    * replace(&quot;aba&quot;, &quot;a&quot;, null)  = &quot;aba&quot;
2747    * replace(&quot;aba&quot;, &quot;a&quot;, &quot;&quot;)    = &quot;b&quot;
2748    * replace(&quot;aba&quot;, &quot;a&quot;, &quot;z&quot;)   = &quot;zbz&quot;
2749    * </pre>
2750    * 
2751    * @see #replace(String text, String repl, String with, int max)
2752    * @param text
2753    *            text to search and replace in, may be null
2754    * @param repl
2755    *            the String to search for, may be null
2756    * @param with
2757    *            the String to replace with, may be null
2758    * @return the text with any replacements processed, <code>null</code> if
2759    *         null String input
2760    */
2761   public static String replace(String text, String repl, String with) {
2762     return replace(text, repl, with, -1);
2763   }
2764 
2765   /**
2766    * <p>
2767    * Replaces a String with another String inside a larger String, for the
2768    * first <code>max</code> values of the search String.
2769    * </p>
2770    * 
2771    * <p>
2772    * A <code>null</code> reference passed to this method is a no-op.
2773    * </p>
2774    * 
2775    * <pre>
2776    * replace(null, *, *, *)         = null
2777    * replace(&quot;&quot;, *, *, *)           = &quot;&quot;
2778    * replace(&quot;any&quot;, null, *, *)     = &quot;any&quot;
2779    * replace(&quot;any&quot;, *, null, *)     = &quot;any&quot;
2780    * replace(&quot;any&quot;, &quot;&quot;, *, *)       = &quot;any&quot;
2781    * replace(&quot;any&quot;, *, *, 0)        = &quot;any&quot;
2782    * replace(&quot;abaa&quot;, &quot;a&quot;, null, -1) = &quot;abaa&quot;
2783    * replace(&quot;abaa&quot;, &quot;a&quot;, &quot;&quot;, -1)   = &quot;b&quot;
2784    * replace(&quot;abaa&quot;, &quot;a&quot;, &quot;z&quot;, 0)   = &quot;abaa&quot;
2785    * replace(&quot;abaa&quot;, &quot;a&quot;, &quot;z&quot;, 1)   = &quot;zbaa&quot;
2786    * replace(&quot;abaa&quot;, &quot;a&quot;, &quot;z&quot;, 2)   = &quot;zbza&quot;
2787    * replace(&quot;abaa&quot;, &quot;a&quot;, &quot;z&quot;, -1)  = &quot;zbzz&quot;
2788    * </pre>
2789    * 
2790    * @param text
2791    *            text to search and replace in, may be null
2792    * @param repl
2793    *            the String to search for, may be null
2794    * @param with
2795    *            the String to replace with, may be null
2796    * @param max
2797    *            maximum number of values to replace, or <code>-1</code> if
2798    *            no maximum
2799    * @return the text with any replacements processed, <code>null</code> if
2800    *         null String input
2801    */
2802   public static String replace(String text, String repl, String with, int max) {
2803     if (text == null || isEmpty(repl) || with == null || max == 0) {
2804       return text;
2805     }
2806 
2807     StringBuffer buf = new StringBuffer(text.length());
2808     int start = 0, end = 0;
2809     while ((end = text.indexOf(repl, start)) != -1) {
2810       buf.append(text.substring(start, end)).append(with);
2811       start = end + repl.length();
2812 
2813       if (--max == 0) {
2814         break;
2815       }
2816     }
2817     buf.append(text.substring(start));
2818     return buf.toString();
2819   }
2820 
2821   /**
2822    * <p>
2823    * Checks if a String is empty ("") or null.
2824    * </p>
2825    * 
2826    * <pre>
2827    * isEmpty(null)      = true
2828    * isEmpty(&quot;&quot;)        = true
2829    * isEmpty(&quot; &quot;)       = false
2830    * isEmpty(&quot;bob&quot;)     = false
2831    * isEmpty(&quot;  bob  &quot;) = false
2832    * </pre>
2833    * 
2834    * <p>
2835    * NOTE: This method changed in Lang version 2.0. It no longer trims the
2836    * String. That functionality is available in isBlank().
2837    * </p>
2838    * 
2839    * @param str
2840    *            the String to check, may be null
2841    * @return <code>true</code> if the String is empty or null
2842    */
2843   public static boolean isEmpty(String str) {
2844     return str == null || str.length() == 0;
2845   }
2846 
2847   /**
2848    * replace a string or strings from a string, and put the output in a string
2849    * buffer. This does not recurse
2850    * 
2851    * @param outBuffer
2852    *            stringbuffer to write to
2853    * @param text
2854    *            string to look in
2855    * @param searchFor
2856    *            string array to search for
2857    * @param replaceWith
2858    *            string array to replace with
2859    */
2860   public static void replace(StringBuffer outBuffer, String text,
2861       Object searchFor, Object replaceWith) {
2862     replace(outBuffer, null, text, searchFor, replaceWith, false, 0, false);
2863   }
2864 
2865   /**
2866    * replace a string or strings from a string, and put the output in a string
2867    * buffer
2868    * 
2869    * @param outBuffer
2870    *            stringbuffer to write to
2871    * @param text
2872    *            string to look in
2873    * @param searchFor
2874    *            string array to search for
2875    * @param replaceWith
2876    *            string array to replace with
2877    * @param recurse
2878    *            if true then do multiple replaces (on the replacements)
2879    */
2880   public static void replace(StringBuffer outBuffer, String text,
2881       Object searchFor, Object replaceWith, boolean recurse) {
2882     replace(outBuffer, null, text, searchFor, replaceWith, recurse,
2883         recurse ? length(searchFor) : 0, false);
2884   }
2885 
2886   /**
2887    * replace a string with other strings, and either write to outWriter, or
2888    * StringBuffer, and if StringBuffer potentially return a string. If
2889    * outBuffer and outWriter are null, then return the String
2890    * 
2891    * @param outBuffer
2892    *            stringbuffer to write to, or null to not
2893    * @param outWriter
2894    *            Writer to write to, or null to not.
2895    * @param text
2896    *            string to look in
2897    * @param searchFor
2898    *            string array to search for, or string, or list
2899    * @param replaceWith
2900    *            string array to replace with, or string, or list
2901    * @param recurse
2902    *            if true then do multiple replaces (on the replacements)
2903    * @param timeToLive
2904    *            if recursing, prevent endless loops
2905    * @param removeIfFound
2906    *            true if removing from searchFor and replaceWith if found
2907    * @return the String if outBuffer and outWriter are null
2908    * @throws IndexOutOfBoundsException
2909    *             if the lengths of the arrays are not the same (null is ok,
2910    *             and/or size 0)
2911    * @throws IllegalArgumentException
2912    *             if the search is recursive and there is an endless loop due
2913    *             to outputs of one being inputs to another
2914    */
2915   private static String replace(StringBuffer outBuffer, Writer outWriter,
2916       String text, Object searchFor, Object replaceWith, boolean recurse,
2917       int timeToLive, boolean removeIfFound) {
2918 
2919     // if recursing, we need to get the string, then print to buffer (since
2920     // we need multiple passes)
2921     if (!recurse) {
2922       return replaceHelper(outBuffer, outWriter, text, searchFor,
2923           replaceWith, recurse, timeToLive, removeIfFound);
2924     }
2925     // get the string
2926     String result = replaceHelper(null, null, text, searchFor, replaceWith,
2927         recurse, timeToLive, removeIfFound);
2928     if (outBuffer != null) {
2929       outBuffer.append(result);
2930       return null;
2931     }
2932 
2933     if (outWriter != null) {
2934       try {
2935         outWriter.write(result);
2936       } catch (IOException ioe) {
2937         throw new RuntimeException(ioe);
2938       }
2939       return null;
2940     }
2941 
2942     return result;
2943 
2944   }
2945 
2946   /**
2947    * replace a string or strings from a string, and put the output in a string
2948    * buffer. This does not recurse
2949    * 
2950    * @param outWriter
2951    *            writer to write to
2952    * @param text
2953    *            string to look in
2954    * @param searchFor
2955    *            string array to search for
2956    * @param replaceWith
2957    *            string array to replace with
2958    */
2959   public static void replace(Writer outWriter, String text, Object searchFor,
2960       Object replaceWith) {
2961     replace(null, outWriter, text, searchFor, replaceWith, false, 0, false);
2962   }
2963 
2964   /**
2965    * replace a string or strings from a string, and put the output in a string
2966    * buffer
2967    * 
2968    * @param outWriter
2969    *            writer to write to
2970    * @param text
2971    *            string to look in
2972    * @param searchFor
2973    *            string array to search for
2974    * @param replaceWith
2975    *            string array to replace with
2976    * @param recurse
2977    *            if true then do multiple replaces (on the replacements)
2978    */
2979   public static void replace(Writer outWriter, String text, Object searchFor,
2980       Object replaceWith, boolean recurse) {
2981     replace(null, outWriter, text, searchFor, replaceWith, recurse,
2982         recurse ? length(searchFor) : 0, false);
2983   }
2984 
2985   /**
2986    * replace a string with other strings, and either write to outWriter, or
2987    * StringBuffer, and if StringBuffer potentially return a string. If
2988    * outBuffer and outWriter are null, then return the String
2989    * 
2990    * @param outBuffer
2991    *            stringbuffer to write to, or null to not
2992    * @param outWriter
2993    *            Writer to write to, or null to not.
2994    * @param text
2995    *            string to look in
2996    * @param searchFor
2997    *            string array to search for, or string, or list
2998    * @param replaceWith
2999    *            string array to replace with, or string, or list
3000    * @param recurse
3001    *            if true then do multiple replaces (on the replacements)
3002    * @param timeToLive
3003    *            if recursing, prevent endless loops
3004    * @param removeIfFound
3005    *            true if removing from searchFor and replaceWith if found
3006    * @return the String if outBuffer and outWriter are null
3007    * @throws IllegalArgumentException
3008    *             if the search is recursive and there is an endless loop due
3009    *             to outputs of one being inputs to another
3010    * @throws IndexOutOfBoundsException
3011    *             if the lengths of the arrays are not the same (null is ok,
3012    *             and/or size 0)
3013    */
3014   private static String replaceHelper(StringBuffer outBuffer,
3015       Writer outWriter, String text, Object searchFor,
3016       Object replaceWith, boolean recurse, int timeToLive,
3017       boolean removeIfFound) {
3018 
3019     try {
3020       // if recursing, this shouldnt be less than 0
3021       if (timeToLive < 0) {
3022         throw new IllegalArgumentException("TimeToLive under 0: "
3023             + timeToLive + ", " + text);
3024       }
3025 
3026       int searchForLength = length(searchFor);
3027       boolean done = false;
3028       // no need to do anything
3029       if (isEmpty(text)) {
3030         return text;
3031       }
3032       // need to write the input to output, later
3033       if (searchForLength == 0) {
3034         done = true;
3035       }
3036 
3037       boolean[] noMoreMatchesForReplIndex = null;
3038       int inputIndex = -1;
3039       int replaceIndex = -1;
3040       long resultPacked = -1;
3041 
3042       if (!done) {
3043         // make sure lengths are ok, these need to be equal
3044         if (searchForLength != length(replaceWith)) {
3045           throw new IndexOutOfBoundsException("Lengths dont match: "
3046               + searchForLength + ", " + length(replaceWith));
3047         }
3048 
3049         // keep track of which still have matches
3050         noMoreMatchesForReplIndex = new boolean[searchForLength];
3051 
3052         // index of replace array that will replace the search string
3053         // found
3054         
3055 
3056         resultPacked = findNextIndexHelper(searchForLength, searchFor,
3057             replaceWith, 
3058             noMoreMatchesForReplIndex, text, 0);
3059 
3060         inputIndex = unpackInt(resultPacked, true);
3061         replaceIndex = unpackInt(resultPacked, false);
3062       }
3063 
3064       // get a good guess on the size of the result buffer so it doesnt
3065       // have to double if it
3066       // goes over a bit
3067       boolean writeToWriter = outWriter != null;
3068 
3069       // no search strings found, we are done
3070       if (done || inputIndex == -1) {
3071         if (writeToWriter) {
3072           outWriter.write(text, 0, text.length());
3073           return null;
3074         }
3075         if (outBuffer != null) {
3076           appendSubstring(outBuffer, text, 0, text.length());
3077           return null;
3078         }
3079         return text;
3080       }
3081 
3082       // no buffer if writing to writer
3083       StringBuffer bufferToWriteTo = outBuffer != null ? outBuffer
3084           : (writeToWriter ? null : new StringBuffer(text.length()
3085               + replaceStringsBufferIncrease(text, searchFor,
3086                   replaceWith)));
3087 
3088       String searchString = null;
3089       String replaceString = null;
3090 
3091       int start = 0;
3092 
3093       while (inputIndex != -1) {
3094 
3095         searchString = (String) get(searchFor, replaceIndex);
3096         replaceString = (String) get(replaceWith, replaceIndex);
3097         if (writeToWriter) {
3098           outWriter.write(text, start, inputIndex - start);
3099           outWriter.write(replaceString);
3100         } else {
3101           appendSubstring(bufferToWriteTo, text, start, inputIndex)
3102               .append(replaceString);
3103         }
3104 
3105         if (removeIfFound) {
3106           // better be an iterator based find replace
3107           searchFor = remove(searchFor, replaceIndex);
3108           replaceWith = remove(replaceWith, replaceIndex);
3109           noMoreMatchesForReplIndex = (boolean[])remove(noMoreMatchesForReplIndex, replaceIndex);
3110           // we now have a lesser size if we removed one
3111           searchForLength--;
3112         }
3113 
3114         start = inputIndex + searchString.length();
3115 
3116         resultPacked = findNextIndexHelper(searchForLength, searchFor,
3117             replaceWith, 
3118             noMoreMatchesForReplIndex, text, start);
3119         inputIndex = unpackInt(resultPacked, true);
3120         replaceIndex = unpackInt(resultPacked, false);
3121       }
3122       if (writeToWriter) {
3123         outWriter.write(text, start, text.length() - start);
3124 
3125       } else {
3126         appendSubstring(bufferToWriteTo, text, start, text.length());
3127       }
3128 
3129       // no need to convert to string if incoming buffer or writer
3130       if (writeToWriter || outBuffer != null) {
3131         if (recurse) {
3132           throw new IllegalArgumentException(
3133               "Cannot recurse and write to existing buffer or writer!");
3134         }
3135         return null;
3136       }
3137       String resultString = bufferToWriteTo.toString();
3138 
3139       if (recurse) {
3140         return replaceHelper(outBuffer, outWriter, resultString,
3141             searchFor, replaceWith, recurse, timeToLive - 1, false);
3142       }
3143       // this might be null for writer
3144       return resultString;
3145     } catch (IOException ioe) {
3146       throw new RuntimeException(ioe);
3147     }
3148   }
3149 
3150   /**
3151    * give a best guess on buffer increase for String[] replace get a good
3152    * guess on the size of the result buffer so it doesnt have to double if it
3153    * goes over a bit
3154    * 
3155    * @param text
3156    * @param repl
3157    * @param with
3158    * @return the increase, with 20% cap
3159    */
3160   static int replaceStringsBufferIncrease(String text, Object repl,
3161       Object with) {
3162     // count the greaters
3163     int increase = 0;
3164     Iterator iteratorReplace = iterator(repl);
3165     Iterator iteratorWith = iterator(with);
3166     int replLength = length(repl);
3167     String currentRepl = null;
3168     String currentWith = null;
3169     for (int i = 0; i < replLength; i++) {
3170       currentRepl = (String) next(repl, iteratorReplace, i);
3171       currentWith = (String) next(with, iteratorWith, i);
3172       if (currentRepl == null || currentWith == null) {
3173         throw new NullPointerException("Replace string is null: "
3174             + text + ", " + currentRepl + ", " + currentWith);
3175       }
3176       int greater = currentWith.length() - currentRepl.length();
3177       increase += greater > 0 ? 3 * greater : 0; // assume 3 matches
3178     }
3179     // have upper-bound at 20% increase, then let Java take over
3180     increase = Math.min(increase, text.length() / 5);
3181     return increase;
3182   }
3183 
3184   /**
3185    * Helper method to find the next match in an array of strings replace
3186    * 
3187    * @param searchForLength
3188    * @param searchFor
3189    * @param replaceWith
3190    * @param noMoreMatchesForReplIndex
3191    * @param input
3192    * @param start
3193    *            is where to start looking
3194    * @return result packed into a long, inputIndex first, then replaceIndex
3195    */
3196   private static long findNextIndexHelper(int searchForLength,
3197       Object searchFor, Object replaceWith, boolean[] noMoreMatchesForReplIndex,
3198       String input, int start) {
3199 
3200     int inputIndex = -1;
3201     int replaceIndex = -1;
3202 
3203     Iterator iteratorSearchFor = iterator(searchFor);
3204     Iterator iteratorReplaceWith = iterator(replaceWith);
3205 
3206     String currentSearchFor = null;
3207     String currentReplaceWith = null;
3208     int tempIndex = -1;
3209     for (int i = 0; i < searchForLength; i++) {
3210       currentSearchFor = (String) next(searchFor, iteratorSearchFor, i);
3211       currentReplaceWith = (String) next(replaceWith,
3212           iteratorReplaceWith, i);
3213       if (noMoreMatchesForReplIndex[i] || isEmpty(currentSearchFor)
3214           || currentReplaceWith == null) {
3215         continue;
3216       }
3217       tempIndex = input.indexOf(currentSearchFor, start);
3218 
3219       // see if we need to keep searching for this
3220       noMoreMatchesForReplIndex[i] = tempIndex == -1;
3221 
3222       if (tempIndex != -1 && (inputIndex == -1 || tempIndex < inputIndex)) {
3223         inputIndex = tempIndex;
3224         replaceIndex = i;
3225       }
3226 
3227     }
3228     // dont create an array, no more objects
3229     long resultPacked = packInts(inputIndex, replaceIndex);
3230     return resultPacked;
3231   }
3232 
3233   /**
3234    * pack two ints into a long. Note: the first is held in the left bits, the
3235    * second is held in the right bits
3236    * 
3237    * @param first
3238    *            is first int
3239    * @param second
3240    *            is second int
3241    * @return the long which has two ints in there
3242    */
3243   public static long packInts(int first, int second) {
3244     long result = first;
3245     result <<= 32;
3246     result |= second;
3247     return result;
3248   }
3249 
3250   /**
3251    * take a long
3252    * 
3253    * @param theLong
3254    *            to unpack
3255    * @param isFirst
3256    *            true for first, false for second
3257    * @return one of the packed ints, first or second
3258    */
3259   public static int unpackInt(long theLong, boolean isFirst) {
3260 
3261     int result = 0;
3262     // put this in the position of the second one
3263     if (isFirst) {
3264       theLong >>= 32;
3265     }
3266     // only look at right part
3267     result = (int) (theLong & 0xffffffff);
3268     return result;
3269   }
3270 
3271   /**
3272    * append a substring to a stringbuffer. removes dependency on substring
3273    * which creates objects
3274    * 
3275    * @param buf
3276    *            stringbuffer
3277    * @param string
3278    *            source string
3279    * @param start
3280    *            start index of source string
3281    * @param end
3282    *            end index of source string
3283    * @return the string buffer for chaining
3284    */
3285   private static StringBuffer appendSubstring(StringBuffer buf,
3286       String string, int start, int end) {
3287     for (int i = start; i < end; i++) {
3288       buf.append(string.charAt(i));
3289     }
3290     return buf;
3291   }
3292 
3293   /**
3294    * Get a specific index of an array or collection (note for collections and
3295    * iterating, it is more efficient to get an iterator and iterate
3296    * 
3297    * @param arrayOrCollection
3298    * @param index
3299    * @return the object at that index
3300    */
3301   public static Object get(Object arrayOrCollection, int index) {
3302 
3303     if (arrayOrCollection == null) {
3304       if (index == 0) {
3305         return null;
3306       }
3307       throw new RuntimeException("Trying to access index " + index
3308           + " of null");
3309     }
3310 
3311     // no need to iterator on list (e.g. FastProxyList has no iterator
3312     if (arrayOrCollection instanceof List) {
3313       return ((List) arrayOrCollection).get(index);
3314     }
3315     if (arrayOrCollection instanceof Collection) {
3316       Iterator iterator = iterator(arrayOrCollection);
3317       for (int i = 0; i < index; i++) {
3318         next(arrayOrCollection, iterator, i);
3319       }
3320       return next(arrayOrCollection, iterator, index);
3321     }
3322 
3323     if (arrayOrCollection.getClass().isArray()) {
3324       return Array.get(arrayOrCollection, index);
3325     }
3326 
3327     if (index == 0) {
3328       return arrayOrCollection;
3329     }
3330 
3331     throw new RuntimeException("Trying to access index " + index
3332         + " of and object: " + arrayOrCollection);
3333   }
3334 
3335 
3336   
3337   /**
3338    * fail safe toString for Exception blocks, and include the stack
3339    * if there is a problem with toString()
3340    * @param object
3341    * @return the toStringSafe string
3342    */
3343   @SuppressWarnings("unchecked")
3344   public static String toStringSafe(Object object) {
3345     if (object == null) {
3346       return null;
3347     }
3348     
3349     try {
3350       //give size and type if collection
3351       if (object instanceof Collection) {
3352         Collection<Object> collection = (Collection<Object>) object;
3353         int collectionSize = collection.size();
3354         if (collectionSize == 0) {
3355           return "Empty " + object.getClass().getSimpleName();
3356         }
3357         Object first = collection.iterator().next();
3358         return object.getClass().getSimpleName() + " of size " 
3359           + collectionSize + " with first type: " + 
3360           (first == null ? null : first.getClass());
3361       }
3362     
3363       return object.toString();
3364     } catch (Exception e) {
3365       return "<<exception>> " + object.getClass() + ":\n" + getFullStackTrace(e) + "\n";
3366     }
3367   }
3368 
3369   /**
3370    * get the boolean value for an object, cant be null or blank
3371    * 
3372    * @param object
3373    * @return the boolean
3374    */
3375   public static boolean booleanValue(Object object) {
3376     // first handle blanks
3377     if (nullOrBlank(object)) {
3378       throw new RuntimeException(
3379           "Expecting something which can be converted to boolean, but is null or blank: '"
3380               + object + "'");
3381     }
3382     // its not blank, just convert
3383     if (object instanceof Boolean) {
3384       return (Boolean) object;
3385     }
3386     if (object instanceof String) {
3387       String string = (String) object;
3388       if (equalsIgnoreCase(string, "true")
3389           || equalsIgnoreCase(string, "t")
3390           || equalsIgnoreCase(string, "yes")
3391           || equalsIgnoreCase(string, "y")) {
3392         return true;
3393       }
3394       if (equalsIgnoreCase(string, "false")
3395           || equalsIgnoreCase(string, "f")
3396           || equalsIgnoreCase(string, "no")
3397           || equalsIgnoreCase(string, "n")) {
3398         return false;
3399       }
3400       throw new RuntimeException(
3401           "Invalid string to boolean conversion: '" + string
3402               + "' expecting true|false or t|f or yes|no or y|n case insensitive");
3403   
3404     }
3405     throw new RuntimeException("Cant convert object to boolean: "
3406         + object.getClass());
3407   
3408   }
3409 
3410   /**
3411    * get the boolean value for an object
3412    * 
3413    * @param object
3414    * @param defaultBoolean
3415    *            if object is null or empty
3416    * @return the boolean
3417    */
3418   public static boolean booleanValue(Object object, boolean defaultBoolean) {
3419     if (nullOrBlank(object)) {
3420       return defaultBoolean;
3421     }
3422     return booleanValue(object);
3423   }
3424 
3425   /**
3426    * get the Boolean value for an object
3427    * 
3428    * @param object
3429    * @return the Boolean or null if null or empty
3430    */
3431   public static Boolean booleanObjectValue(Object object) {
3432     if (nullOrBlank(object)) {
3433       return null;
3434     }
3435     return booleanValue(object);
3436   }
3437 
3438   /**
3439    * is an object null or blank
3440    * 
3441    * @param object
3442    * @return true if null or blank
3443    */
3444   public static boolean nullOrBlank(Object object) {
3445     // first handle blanks and nulls
3446     if (object == null) {
3447       return true;
3448     }
3449     if (object instanceof String && isBlank(((String) object))) {
3450       return true;
3451     }
3452     return false;
3453   
3454   }
3455 
3456   /**
3457    * get a getter method object for a class, potentially in superclasses
3458    * @param theClass
3459    * @param fieldName
3460    * @param callOnSupers true if superclasses should be looked in for the getter
3461    * @param throwExceptionIfNotFound will throw runtime exception if not found
3462    * @return the getter object or null if not found (or exception if param is set)
3463    */
3464   public static Method getter(Class theClass, String fieldName, boolean callOnSupers, 
3465       boolean throwExceptionIfNotFound) {
3466     String getterName = getterNameFromPropertyName(fieldName);
3467     return getterHelper(theClass, fieldName, getterName, callOnSupers, throwExceptionIfNotFound);
3468   }
3469 
3470   /**
3471    * get a setter method object for a class, potentially in superclasses
3472    * @param theClass
3473    * @param fieldName
3474    * @param getterName name of setter
3475    * @param callOnSupers true if superclasses should be looked in for the setter
3476    * @param throwExceptionIfNotFound will throw runtime exception if not found
3477    * @return the setter object or null if not found (or exception if param is set)
3478    */
3479   public static Method getterHelper(Class theClass, String fieldName, String getterName, 
3480       boolean callOnSupers, boolean throwExceptionIfNotFound) {
3481     Method[] methods = retrieveDeclaredMethods(theClass);
3482     if (methods != null) {
3483       for (Method method : methods) {
3484         if (equals(getterName, method.getName()) && isGetter(method)) {
3485           return method;
3486         }
3487       }
3488     }
3489     //if method not found
3490     //if traversing up, and not Object, and not instance method
3491     if (callOnSupers && !theClass.equals(Object.class)) {
3492       return getterHelper(theClass.getSuperclass(), fieldName, getterName, 
3493           callOnSupers, throwExceptionIfNotFound);
3494     }
3495     //maybe throw an exception
3496     if (throwExceptionIfNotFound) {
3497       throw new RuntimeException("Cant find getter: "
3498           + getterName + ", in: " + theClass
3499           + ", callOnSupers: " + callOnSupers);
3500     }
3501     return null;
3502   }
3503 
3504   /**
3505    * generate getBb from bb
3506    * @param propertyName
3507    * @return the getter 
3508    */
3509   public static String getterNameFromPropertyName(String propertyName) {
3510     return "get" + capitalize(propertyName);
3511   }
3512 
3513   /**
3514    * get all getters from a class, including superclasses (if specified) (up to and including the specified superclass).  
3515    * ignore a certain marker annotation, or only include it.
3516    * Dont get static or final getters, and get getters of all types
3517    * @param theClass to look for fields in
3518    * @param superclassToStopAt to go up to or null to go up to Object
3519    * @param markerAnnotation if this is not null, then if the field has this annotation, then do not
3520    * include in list (if includeAnnotation is false)
3521    * @param includeAnnotation true if the attribute should be included if annotation is present, false if exclude
3522    * @return the set of field names or empty set if none
3523    */
3524   @SuppressWarnings("unchecked")
3525   public static Set<Method> getters(Class theClass, Class superclassToStopAt,
3526       Class<? extends Annotation> markerAnnotation, Boolean includeAnnotation) {
3527     return gettersHelper(theClass, superclassToStopAt, null, true, 
3528         markerAnnotation, includeAnnotation);
3529   }
3530 
3531   /**
3532    * get all getters from a class, including superclasses (if specified)
3533    * @param theClass to look for fields in
3534    * @param superclassToStopAt to go up to or null to go up to Object
3535    * @param fieldType is the type of the field to get
3536    * @param includeSuperclassToStopAt if we should include the superclass
3537    * @param markerAnnotation if this is not null, then if the field has this annotation, then do not
3538    * include in list (if includeAnnotation is false)
3539    * @param includeAnnotation true if the attribute should be included if annotation is present, false if exclude
3540    * @return the set of fields (wont return null)
3541    */
3542   @SuppressWarnings("unchecked")
3543   static Set<Method> gettersHelper(Class theClass, Class superclassToStopAt, Class<?> fieldType,
3544       boolean includeSuperclassToStopAt, 
3545       Class<? extends Annotation> markerAnnotation, Boolean includeAnnotation) {
3546     //MAKE SURE IF ANY MORE PARAMS ARE ADDED, THE CACHE KEY IS CHANGED!
3547     
3548     Set<Method> getterSet = null;
3549     String cacheKey = theClass + CACHE_SEPARATOR + superclassToStopAt + CACHE_SEPARATOR + fieldType + CACHE_SEPARATOR
3550       + includeSuperclassToStopAt + CACHE_SEPARATOR + markerAnnotation + CACHE_SEPARATOR + includeAnnotation;
3551     getterSet = getterSetCache().get(cacheKey);
3552     if (getterSet != null) {
3553       return getterSet;
3554     }
3555     
3556     getterSet = new LinkedHashSet<Method>();
3557     gettersHelper(theClass, superclassToStopAt, fieldType, includeSuperclassToStopAt, 
3558         markerAnnotation, getterSet, includeAnnotation);
3559   
3560     //add to cache
3561     getterSetCache().put(cacheKey, getterSet);
3562     
3563     return getterSet;
3564     
3565   }
3566 
3567   /**
3568    * get all getters from a class, including superclasses (if specified)
3569    * @param theClass to look for fields in
3570    * @param superclassToStopAt to go up to or null to go up to Object
3571    * @param propertyType is the type of the field to get
3572    * @param includeSuperclassToStopAt if we should include the superclass
3573    * @param markerAnnotation if this is not null, then if the field has this annotation, then do not
3574    * include in list
3575    * @param getterSet set to add fields to
3576    * @param includeAnnotation if include or exclude
3577    */
3578   @SuppressWarnings("unchecked")
3579   private static void gettersHelper(Class theClass, Class superclassToStopAt, Class<?> propertyType,
3580       boolean includeSuperclassToStopAt,  
3581       Class<? extends Annotation> markerAnnotation, Set<Method> getterSet, Boolean includeAnnotation) {
3582 
3583     Method[] methods = retrieveDeclaredMethods(theClass);
3584     if (length(methods) != 0) {
3585       for (Method method: methods) {
3586         //must be a getter
3587         if (!isGetter(method)) {
3588           continue;
3589         }
3590         //if checking for annotation
3591         if (markerAnnotation != null
3592             && (includeAnnotation != method.isAnnotationPresent(markerAnnotation))) {
3593           continue;
3594         }
3595         //if checking for type, and not right type, continue
3596         if (propertyType != null && !propertyType.isAssignableFrom(method.getReturnType())) {
3597           continue;
3598         }
3599         
3600         //go for it
3601         getterSet.add(method);
3602       }
3603     }
3604     //see if done recursing (if superclassToStopAt is null, then stop at Object
3605     if (theClass.equals(superclassToStopAt) || theClass.equals(Object.class)) {
3606       return;
3607     }
3608     Class superclass = theClass.getSuperclass();
3609     if (!includeSuperclassToStopAt && superclass.equals(superclassToStopAt)) {
3610       return;
3611     }
3612     //recurse
3613     gettersHelper(superclass, superclassToStopAt, propertyType, 
3614         includeSuperclassToStopAt, markerAnnotation, getterSet,
3615         includeAnnotation);
3616   }
3617 
3618   /**
3619    * if the method name starts with get, and takes no args, and returns something, 
3620    * then getter
3621    * @param method 
3622    * @return true if getter
3623    */
3624   public static boolean isGetter(Method method) {
3625     
3626     //must start with get
3627     String methodName = method.getName();
3628     if (!methodName.startsWith("get") && !methodName.startsWith("is")) {
3629       return false;
3630     }
3631   
3632     //must not be void
3633     if (method.getReturnType() == Void.TYPE) {
3634       return false;
3635     }
3636     
3637     //must not take args
3638     if (length(method.getParameterTypes()) != 0) {
3639       return false;
3640     }
3641     
3642     //must not be static
3643     if (Modifier.isStatic(method.getModifiers())) {
3644       return false;
3645     }
3646     
3647     return true;
3648   }
3649 
3650   /**
3651    * assign data to a setter.  Will find the field in superclasses, will typecast, 
3652    * and will override security (private, protected, etc)
3653    * @param invokeOn to call on or null for static
3654    * @param fieldName method name to call
3655    * @param dataToAssign data  
3656    * @param typeCast will typecast if true
3657    * @throws RuntimeException if not there
3658    */
3659   public static void assignSetter(Object invokeOn, 
3660       String fieldName, Object dataToAssign, boolean typeCast) {
3661     Class invokeOnClass = invokeOn.getClass();
3662     try {
3663       Method setter = setter(invokeOnClass, fieldName, true, true);
3664       setter.setAccessible(true);
3665       if (typeCast) {
3666         dataToAssign = typeCast(dataToAssign, setter.getParameterTypes()[0]);
3667       }
3668       setter.invoke(invokeOn, new Object[]{dataToAssign});
3669     } catch (Exception e) {
3670       throw new RuntimeException("Problem assigning setter: " + fieldName
3671           + " on class: " + invokeOnClass + ", type of data is: " + className(dataToAssign), e);
3672     }
3673   }
3674 
3675   /**
3676    * if the method name starts with get, and takes no args, and returns something, 
3677    * then getter
3678    * @param method 
3679    * @return true if getter
3680    */
3681   public static boolean isSetter(Method method) {
3682     
3683     //must start with get
3684     if (!method.getName().startsWith("set")) {
3685       return false;
3686     }
3687   
3688     //must be void
3689     if (method.getReturnType() != Void.TYPE) {
3690       return false;
3691     }
3692     
3693     //must take one arg
3694     if (length(method.getParameterTypes()) != 1) {
3695       return false;
3696     }
3697     
3698     //must not be static
3699     if (Modifier.isStatic(method.getModifiers())) {
3700       return false;
3701     }
3702     
3703     return true;
3704   }
3705 
3706   /**
3707    * get a setter method object for a class, potentially in superclasses
3708    * @param theClass
3709    * @param fieldName
3710    * @param callOnSupers true if superclasses should be looked in for the setter
3711    * @param throwExceptionIfNotFound will throw runtime exception if not found
3712    * @return the setter object or null if not found (or exception if param is set)
3713    */
3714   public static Method setter(Class theClass, String fieldName, boolean callOnSupers, 
3715       boolean throwExceptionIfNotFound) {
3716     String setterName = setterNameFromPropertyName(fieldName);
3717     return setterHelper(theClass, fieldName, setterName, callOnSupers, throwExceptionIfNotFound);
3718   }
3719 
3720   /**
3721    * get a setter method object for a class, potentially in superclasses
3722    * @param theClass
3723    * @param fieldName
3724    * @param setterName name of setter
3725    * @param callOnSupers true if superclasses should be looked in for the setter
3726    * @param throwExceptionIfNotFound will throw runtime exception if not found
3727    * @return the setter object or null if not found (or exception if param is set)
3728    */
3729   public static Method setterHelper(Class theClass, String fieldName, String setterName, 
3730       boolean callOnSupers, boolean throwExceptionIfNotFound) {
3731     Method[] methods = retrieveDeclaredMethods(theClass);
3732     if (methods != null) {
3733       for (Method method : methods) {
3734         if (equals(setterName, method.getName()) && isSetter(method)) {
3735           return method;
3736         }
3737       }
3738     }
3739     //if method not found
3740     //if traversing up, and not Object, and not instance method
3741     if (callOnSupers && !theClass.equals(Object.class)) {
3742       return setterHelper(theClass.getSuperclass(), fieldName, setterName, 
3743           callOnSupers, throwExceptionIfNotFound);
3744     }
3745     //maybe throw an exception
3746     if (throwExceptionIfNotFound) {
3747       throw new RuntimeException("Cant find setter: "
3748           + setterName + ", in: " + theClass
3749           + ", callOnSupers: " + callOnSupers);
3750     }
3751     return null;
3752   }
3753 
3754   /**
3755    * generate setBb from bb
3756    * @param propertyName
3757    * @return the setter 
3758    */
3759   public static String setterNameFromPropertyName(String propertyName) {
3760     return "set" + capitalize(propertyName);
3761   }
3762 
3763   /**
3764    * get all setters from a class, including superclasses (if specified)
3765    * @param theClass to look for fields in
3766    * @param superclassToStopAt to go up to or null to go up to Object
3767    * @param fieldType is the type of the field to get
3768    * @param includeSuperclassToStopAt if we should include the superclass
3769    * @param markerAnnotation if this is not null, then if the field has this annotation, then do not
3770    * include in list (if includeAnnotation is false)
3771    * @param includeAnnotation true if the attribute should be included if annotation is present, false if exclude
3772    * @return the set of fields (wont return null)
3773    */
3774   @SuppressWarnings("unchecked")
3775   public static Set<Method> setters(Class theClass, Class superclassToStopAt, Class<?> fieldType,
3776       boolean includeSuperclassToStopAt, 
3777       Class<? extends Annotation> markerAnnotation, boolean includeAnnotation) {
3778     return settersHelper(theClass, superclassToStopAt, fieldType, 
3779         includeSuperclassToStopAt, markerAnnotation, includeAnnotation);
3780   }
3781 
3782   /**
3783    * get all setters from a class, including superclasses (if specified)
3784    * @param theClass to look for fields in
3785    * @param superclassToStopAt to go up to or null to go up to Object
3786    * @param fieldType is the type of the field to get
3787    * @param includeSuperclassToStopAt if we should include the superclass
3788    * @param markerAnnotation if this is not null, then if the field has this annotation, then do not
3789    * include in list (if includeAnnotation is false)
3790    * @param includeAnnotation true if the attribute should be included if annotation is present, false if exclude
3791    * @return the set of fields (wont return null)
3792    */
3793   @SuppressWarnings("unchecked")
3794   static Set<Method> settersHelper(Class theClass, Class superclassToStopAt, Class<?> fieldType,
3795       boolean includeSuperclassToStopAt, 
3796       Class<? extends Annotation> markerAnnotation, boolean includeAnnotation) {
3797     //MAKE SURE IF ANY MORE PARAMS ARE ADDED, THE CACHE KEY IS CHANGED!
3798     
3799     Set<Method> setterSet = null;
3800     String cacheKey = theClass + CACHE_SEPARATOR + superclassToStopAt + CACHE_SEPARATOR + fieldType + CACHE_SEPARATOR
3801       + includeSuperclassToStopAt + CACHE_SEPARATOR + markerAnnotation + CACHE_SEPARATOR + includeAnnotation;
3802     setterSet = setterSetCache().get(cacheKey);
3803     if (setterSet != null) {
3804       return setterSet;
3805     }
3806     
3807     setterSet = new LinkedHashSet<Method>();
3808     settersHelper(theClass, superclassToStopAt, fieldType, includeSuperclassToStopAt, 
3809         markerAnnotation, setterSet, includeAnnotation);
3810   
3811     //add to cache
3812     setterSetCache().put(cacheKey, setterSet);
3813     
3814     return setterSet;
3815     
3816   }
3817 
3818   /**
3819    * get all setters from a class, including superclasses (if specified)
3820    * @param theClass to look for fields in
3821    * @param superclassToStopAt to go up to or null to go up to Object
3822    * @param propertyType is the type of the field to get
3823    * @param includeSuperclassToStopAt if we should include the superclass
3824    * @param markerAnnotation if this is not null, then if the field has this annotation, then do not
3825    * include in list
3826    * @param setterSet set to add fields to
3827    * @param includeAnnotation if include or exclude (or null if not looking for annotations)
3828    */
3829   @SuppressWarnings("unchecked")
3830   private static void settersHelper(Class theClass, Class superclassToStopAt, Class<?> propertyType,
3831       boolean includeSuperclassToStopAt,  
3832       Class<? extends Annotation> markerAnnotation, Set<Method> setterSet, Boolean includeAnnotation) {
3833 
3834     Method[] methods = retrieveDeclaredMethods(theClass);
3835     if (length(methods) != 0) {
3836       for (Method method: methods) {
3837         //must be a getter
3838         if (!isSetter(method)) {
3839           continue;
3840         }
3841         //if checking for annotation
3842         if (markerAnnotation != null
3843             && (includeAnnotation != method.isAnnotationPresent(markerAnnotation))) {
3844           continue;
3845         }
3846         //if checking for type, and not right type, continue
3847         if (propertyType != null && !propertyType.isAssignableFrom(method.getParameterTypes()[0])) {
3848           continue;
3849         }
3850         
3851         //go for it
3852         setterSet.add(method);
3853       }
3854     }
3855     //see if done recursing (if superclassToStopAt is null, then stop at Object
3856     if (theClass.equals(superclassToStopAt) || theClass.equals(Object.class)) {
3857       return;
3858     }
3859     Class superclass = theClass.getSuperclass();
3860     if (!includeSuperclassToStopAt && superclass.equals(superclassToStopAt)) {
3861       return;
3862     }
3863     //recurse
3864     settersHelper(superclass, superclassToStopAt, propertyType, 
3865         includeSuperclassToStopAt, markerAnnotation, setterSet,
3866         includeAnnotation);
3867   }
3868 
3869   /**
3870    * If this is a getter or setter, then get the property name
3871    * @param method
3872    * @return the property name
3873    */
3874   public static String propertyName(Method method) {
3875     String methodName = method.getName();
3876     boolean isGetter = methodName.startsWith("get");
3877     boolean isSetter = methodName.startsWith("set");
3878     boolean isIsser = methodName.startsWith("is");
3879     int expectedLength = isIsser ? 2 : 3; 
3880     int length = methodName.length();
3881     if ((!(isGetter || isSetter || isIsser)) || (length <= expectedLength)) {
3882       throw new RuntimeException("Not a getter or setter: " + methodName);
3883     }
3884     char fourthCharLower = Character.toLowerCase(methodName.charAt(expectedLength));
3885     //if size 4, then return the string
3886     if (length == expectedLength +1) {
3887       return Character.toString(fourthCharLower);
3888     }
3889     //return the lower appended with the rest
3890     return fourthCharLower + methodName.substring(expectedLength+1, length);
3891   }
3892 
3893   /**
3894    * use reflection to get a property type based on getter or setter or field
3895    * @param theClass
3896    * @param propertyName
3897    * @return the property type
3898    */
3899   public static Class propertyType(Class theClass, String propertyName) {
3900     Method method = getter(theClass, propertyName, true, false);
3901     if (method != null) {
3902       return method.getReturnType();
3903     }
3904     //use setter
3905     method = setter(theClass, propertyName, true, false);
3906     if (method != null) {
3907       return method.getParameterTypes()[0];
3908     }
3909     //no setter or getter, use field
3910     Field field = field(theClass, propertyName, true, true);
3911     return field.getType();
3912   }
3913 
3914   /**
3915    * If necessary, convert an object to another type.  if type is Object.class, just return the input.
3916    * Do not convert null to an empty primitive
3917    * @param <T> is template type
3918    * @param value
3919    * @param theClass
3920    * @return the object of that instance converted into something else
3921    */
3922   public static <T> T typeCast(Object value, Class<T> theClass) {
3923     //default behavior is not to convert null to empty primitive
3924     return typeCast(value, theClass, false, false);
3925   }
3926 
3927   /**
3928    * <pre>
3929    * make a new file in the name prefix dir.  If parent dir name is c:\temp
3930    * and namePrefix is grouperDdl and nameSuffix is sql, then the file will be:
3931    * 
3932    * c:\temp\grouperDdl_20080721_13_45_43_123.sql
3933    *  
3934    * If the file exists, it will make a new filename, and create the empty file, and return it
3935    * </pre>
3936    *  
3937    * @param parentDirName can be blank for current dir
3938    * @param namePrefix the part before the date part
3939    * @param nameSuffix the last part of file name (can contain dot or will be the extension
3940    * @param createFile true to create the file
3941    * @return the created file
3942    */
3943   public static File newFileUniqueName(String parentDirName, String namePrefix, String nameSuffix, boolean createFile) {
3944     DateFormat fileNameFormat = new SimpleDateFormat("yyyyMMdd_HH_mm_ss_SSS");
3945     if (!isBlank(parentDirName)) {
3946 
3947       if (!parentDirName.endsWith("/") && !parentDirName.endsWith("\\")) {
3948         parentDirName += File.separator;
3949       }
3950       
3951       //make sure it exists and is a dir
3952       File parentDir = new File(parentDirName);
3953       if (!parentDir.exists()) {
3954         if (!parentDir.mkdirs()) {
3955           throw new RuntimeException("Cant make dir: " + parentDir.getAbsolutePath());
3956         }
3957       } else {
3958         if (!parentDir.isDirectory()) {
3959           throw new RuntimeException("Parent dir is not a directory: " + parentDir.getAbsolutePath());
3960         } 
3961       }
3962       
3963     } else {
3964       //make it empty string so it will concatenate well
3965       parentDirName = "";
3966     }
3967     //make sure suffix has a dot in it
3968     if (!nameSuffix.contains(".")) {
3969       nameSuffix = "." + nameSuffix;
3970     }
3971     
3972     String fileName = parentDirName + namePrefix + "_" + fileNameFormat.format(new Date()) + nameSuffix;
3973     int dotLocation = fileName.lastIndexOf('.');
3974     String fileNamePre = fileName.substring(0,dotLocation);
3975     String fileNamePost = fileName.substring(dotLocation);
3976     File theFile = new File(fileName);
3977 
3978     int i;
3979     
3980     for (i=0;i<1000;i++) {
3981       
3982       if (!theFile.exists()) {
3983         break;
3984       }
3985       
3986       fileName = fileNamePre + "_" + i + fileNamePost;
3987       theFile = new File(fileName);
3988       
3989     }
3990     
3991     if (i>=1000) {
3992       throw new RuntimeException("Cant find filename to create: " + fileName);
3993     }
3994     
3995     if (createFile) {
3996       try {
3997         if (!theFile.createNewFile()) {
3998           throw new RuntimeException("Cant create file, it returned false");
3999         }
4000       } catch (Exception e) {
4001         throw new RuntimeException("Cant create file: " + fileName + ", make sure " +
4002             "permissions and such are ok, or change file location in grouper.properties if applicable", e);
4003       }
4004     }
4005     return theFile;
4006   }
4007   
4008   /**
4009    * <pre>
4010    * Convert an object to a java.util.Date.  allows, dates, null, blank, 
4011    * yyyymmdd or yyyymmdd hh24:mm:ss
4012    * or yyyy/MM/dd HH:mm:ss.SSS
4013    * </pre>
4014    * @param inputObject
4015    *          is the String or Date to convert
4016    * 
4017    * @return the Date
4018    */
4019   public static Date dateValue(Object inputObject) {
4020     if (inputObject == null) {
4021       return null;
4022     } 
4023 
4024     if (inputObject instanceof java.util.Date) {
4025       return (Date)inputObject;
4026     }
4027 
4028     if (inputObject instanceof String) {
4029       String input = (String)inputObject;
4030       //trim and handle null and empty
4031       if (isBlank(input)) {
4032         return null;
4033       }
4034 
4035       try {
4036         if (input.length() == 8) {
4037           
4038           return dateFormat().parse(input);
4039         }
4040         if (!contains(input, '.')) {
4041           if (contains(input, '/')) {
4042             return dateMinutesSecondsFormat.parse(input);
4043           }
4044           //else no slash
4045           return dateMinutesSecondsNoSlashFormat.parse(input);
4046         }
4047         if (contains(input, '/')) {
4048           //see if the period is 6 back
4049           int lastDotIndex = input.lastIndexOf('.');
4050           if (lastDotIndex == input.length() - 7) {
4051             String nonNanoInput = input.substring(0,input.length()-3);
4052             Date date = timestampFormat.parse(nonNanoInput);
4053             //get the last 3
4054             String lastThree = input.substring(input.length()-3,input.length());
4055             int lastThreeInt = Integer.parseInt(lastThree);
4056             Timestamp timestamp = new Timestamp(date.getTime());
4057             timestamp.setNanos(timestamp.getNanos() + (lastThreeInt * 1000));
4058             return timestamp;
4059           }
4060           return timestampFormat.parse(input);
4061         }
4062         //else no slash
4063         return timestampNoSlashFormat.parse(input);
4064       } catch (ParseException pe) {
4065         throw new RuntimeException(errorStart + toStringForLog(input));
4066       }
4067     }
4068     
4069     throw new RuntimeException("Cannot convert Object to date : " + toStringForLog(inputObject));
4070   }
4071 
4072   /**
4073    * See if the input is null or if string, if it is empty or blank (whitespace)
4074    * @param input
4075    * @return true if blank
4076    */
4077   public static boolean isBlank(Object input) {
4078     if (null == input) {
4079       return true;
4080     }
4081     return (input instanceof String && isBlank((String)input));
4082   }
4083 
4084   /**
4085    * If necessary, convert an object to another type.  if type is Object.class, just return the input
4086    * @param <T> is the type to return
4087    * @param value
4088    * @param theClass
4089    * @param convertNullToDefaultPrimitive if the value is null, and theClass is primitive, should we
4090    * convert the null to a primitive default value
4091    * @param useNewInstanceHooks if theClass is not recognized, then honor the string "null", "newInstance",
4092    * or get a constructor with one param, and call it
4093    * @return the object of that instance converted into something else
4094    */
4095   @SuppressWarnings({ "unchecked", "cast" })
4096   public static <T> T typeCast(Object value, Class<T> theClass, 
4097       boolean convertNullToDefaultPrimitive, boolean useNewInstanceHooks) {
4098     
4099     if (Object.class.equals(theClass)) {
4100       return (T)value;
4101     }
4102     
4103     if (value==null) {
4104       if (convertNullToDefaultPrimitive && theClass.isPrimitive()) {
4105         if ( theClass == boolean.class ) {
4106           return (T)Boolean.FALSE;
4107         }
4108         if ( theClass == char.class ) {
4109           return (T)(Object)0;
4110         }
4111         //convert 0 to the type
4112         return typeCast(0, theClass, false, false);
4113       }
4114       return null;
4115     }
4116   
4117     if (theClass.isInstance(value)) {
4118       return (T)value;
4119     }
4120     
4121     //if array, get the base class
4122     if (theClass.isArray() && theClass.getComponentType() != null) {
4123       theClass = (Class<T>)theClass.getComponentType();
4124     }
4125     Object resultValue = null;
4126     //loop through and see the primitive types etc
4127     if (theClass.equals(Date.class)) {
4128       resultValue = dateValue(value);
4129     } else if (theClass.equals(String.class)) {
4130       resultValue = stringValue(value);
4131     } else if (theClass.equals(Timestamp.class)) {
4132       resultValue = toTimestamp(value);
4133     } else if (theClass.equals(Boolean.class) || theClass.equals(boolean.class)) {
4134       resultValue = booleanObjectValue(value);
4135     } else if (theClass.equals(Integer.class) || theClass.equals(int.class)) {
4136       resultValue = intObjectValue(value, true);
4137     } else if (theClass.equals(Double.class) || theClass.equals(double.class)) {
4138       resultValue = doubleObjectValue(value, true);
4139     } else if (theClass.equals(Float.class) || theClass.equals(float.class)) {
4140       resultValue = floatObjectValue(value, true);
4141     } else if (theClass.equals(Long.class) || theClass.equals(long.class)) {
4142       resultValue = longObjectValue(value, true);
4143     } else if (theClass.equals(Byte.class) || theClass.equals(byte.class)) {
4144       resultValue = byteObjectValue(value);
4145     } else if (theClass.equals(Character.class) || theClass.equals(char.class)) {
4146       resultValue = charObjectValue(value);
4147     } else if (theClass.equals(Short.class) || theClass.equals(short.class)) {
4148       resultValue = shortObjectValue(value);
4149     } else if ( theClass.isEnum() && (value instanceof String) ) {
4150       resultValue = Enum.valueOf((Class)theClass, (String) value);
4151     } else if ( theClass.equals(Class.class) && (value instanceof String) ) {
4152       resultValue = forName((String)value);
4153     } else if (useNewInstanceHooks && value instanceof String) {
4154       String stringValue = (String)value;
4155       if ( equals("null", stringValue)) {
4156         resultValue = null;
4157       } else if (equals("newInstance", stringValue)) {
4158         resultValue = newInstance(theClass);
4159       } else { // instantiate using string
4160         //note, we could typecast this to fit whatever is there... right now this is used for annotation
4161         try {
4162           Constructor constructor = theClass.getConstructor(new Class[] {String.class} );
4163           resultValue = constructor.newInstance(new Object[] {stringValue} );            
4164         } catch (Exception e) {
4165           throw new RuntimeException("Cant find constructor with string for class: " + theClass);
4166         }
4167       }
4168     } else {
4169       throw new RuntimeException("Cannot convert from type: " + value.getClass() + " to type: " + theClass);
4170     }
4171   
4172     return (T)resultValue;
4173   }
4174   
4175   /**
4176    * see if a class is a scalar (not bean, not array or list, etc)
4177    * @param type
4178    * @return true if scalar
4179    */
4180   public static boolean isScalar(Class<?> type) {
4181     
4182     if (type.isArray()) {
4183       return false;
4184     }
4185     
4186     //definitely all primitives
4187     if (type.isPrimitive()) {
4188       return true;
4189     }
4190     //Integer, Float, etc
4191     if (Number.class.isAssignableFrom(type)) {
4192       return true;
4193     }
4194     //Date, Timestamp
4195     if (Date.class.isAssignableFrom(type)) {
4196       return true;
4197     }
4198     if (Character.class.equals(type)) {
4199       return true;
4200     }
4201     //handles strings and string builders
4202     if (CharSequence.class.equals(type) || CharSequence.class.isAssignableFrom(type)) {
4203       return true;
4204     }
4205     if (Class.class == type || Boolean.class == type || type.isEnum()) {
4206       return true;
4207     }
4208     //appears not to be a scalar
4209     return false;
4210   }
4211   
4212   
4213   /**
4214    * <pre>
4215    * Convert a string or object to a timestamp (could be string, date, timestamp, etc)
4216    * yyyymmdd
4217    * or
4218    * yyyy/MM/dd HH:mm:ss
4219    * or
4220    * yyyy/MM/dd HH:mm:ss.SSS
4221    * or
4222    * yyyy/MM/dd HH:mm:ss.SSSSSS
4223    * 
4224    * </pre>
4225    * 
4226    * @param input
4227    * @return the timestamp 
4228    * @throws RuntimeException if invalid format
4229    */
4230   public static Timestamp toTimestamp(Object input) {
4231 
4232     if (null == input) {
4233       return null;
4234     } else if (input instanceof java.sql.Timestamp) {
4235       return (Timestamp) input;
4236     } else if (input instanceof String) {
4237       return stringToTimestamp((String) input);
4238     } else if (input instanceof Date) {
4239       return new Timestamp(((Date)input).getTime());
4240     } else if (input instanceof java.sql.Date) {
4241       return new Timestamp(((java.sql.Date)input).getTime());
4242     } else {
4243       throw new RuntimeException("Cannot convert Object to timestamp : " + input);
4244     }
4245 
4246   }
4247 
4248   /**
4249    * convert an object to a string
4250    * 
4251    * @param input
4252    *          is the object to convert
4253    * 
4254    * @return the String conversion of the object
4255    */
4256   public static String stringValue(Object input) {
4257     //this isnt needed
4258     if (input == null) {
4259       return (String) input;
4260     }
4261 
4262     if (input instanceof Timestamp) {
4263       //convert to yyyy/MM/dd HH:mm:ss.SSS
4264       return timestampToString((Timestamp) input);
4265     }
4266 
4267     if (input instanceof Date) {
4268       //convert to yyyymmdd
4269       return stringValue((Date) input);
4270     }
4271 
4272     if (input instanceof Number) {
4273       DecimalFormat decimalFormat = new DecimalFormat(
4274           "###################.###############");
4275       return decimalFormat.format(((Number) input).doubleValue());
4276 
4277     }
4278 
4279     return input.toString();
4280   }
4281 
4282   /**
4283    * Convert a timestamp into a string: yyyy/MM/dd HH:mm:ss.SSS
4284    * @param timestamp
4285    * @return the string representation
4286    */
4287   public synchronized static String timestampToString(Date timestamp) {
4288     if (timestamp == null) {
4289       return null;
4290     }
4291     return timestampFormat.format(timestamp);
4292   }
4293 
4294   /**
4295    * get the timestamp format for this thread
4296    * if you call this make sure to synchronize on FastDateUtils.class
4297    * @return the timestamp format
4298    */
4299   synchronized static SimpleDateFormat dateFormat() {
4300     return dateFormat;
4301   }
4302 
4303   /**
4304    * convert a date to the standard string yyyymmdd
4305    * @param date 
4306    * @return the string value
4307    */
4308   public static String stringValue(java.util.Date date) {
4309     synchronized (ConfigPropertiesCascadeCommonUtils.class) {
4310       if (date == null) {
4311         return null;
4312       }
4313   
4314       String theString = dateFormat().format(date);
4315   
4316       return theString;
4317     }
4318   }
4319 
4320   /**
4321    * <pre>convert a string to timestamp based on the following formats:
4322    * yyyyMMdd
4323    * yyyy/MM/dd HH:mm:ss
4324    * yyyy/MM/dd HH:mm:ss.SSS
4325    * yyyy/MM/dd HH:mm:ss.SSSSSS
4326    * </pre>
4327    * @param input
4328    * @return the timestamp object
4329    */
4330   public static Timestamp stringToTimestamp(String input) {
4331     Date date = stringToTimestampHelper(input);
4332     if (date == null) {
4333       return null;
4334     }
4335     //maybe already a timestamp
4336     if (date instanceof Timestamp) {
4337       return (Timestamp)date; 
4338     }
4339     return new Timestamp(date.getTime());
4340   }
4341 
4342   /**
4343    * return a date based on input, null safe.  Allow any of the three 
4344    * formats:
4345    * yyyyMMdd
4346    * yyyy/MM/dd HH:mm:ss
4347    * yyyy/MM/dd HH:mm:ss.SSS
4348    * yyyy/MM/dd HH:mm:ss.SSSSSS
4349    * 
4350    * @param input
4351    * @return the millis, -1 for null
4352    */
4353   synchronized static Date stringToTimestampHelper(String input) {
4354     //trim and handle null and empty
4355     if (isBlank(input)) {
4356       return null;
4357     }
4358   
4359     try {
4360       //convert mainframe
4361       if (equals("99999999", input)
4362           || equals("999999", input)) {
4363         input = "20991231";
4364       }
4365       if (input.length() == 8) {
4366         
4367         return dateFormat().parse(input);
4368       }
4369       if (!contains(input, '.')) {
4370         if (contains(input, '/')) {
4371           return dateMinutesSecondsFormat.parse(input);
4372         }
4373         //else no slash
4374         return dateMinutesSecondsNoSlashFormat.parse(input);
4375       }
4376       if (contains(input, '/')) {
4377         //see if the period is 6 back
4378         int lastDotIndex = input.lastIndexOf('.');
4379         if (lastDotIndex == input.length() - 7) {
4380           String nonNanoInput = input.substring(0,input.length()-3);
4381           Date date = timestampFormat.parse(nonNanoInput);
4382           //get the last 3
4383           String lastThree = input.substring(input.length()-3,input.length());
4384           int lastThreeInt = Integer.parseInt(lastThree);
4385           Timestamp timestamp = new Timestamp(date.getTime());
4386           timestamp.setNanos(timestamp.getNanos() + (lastThreeInt * 1000));
4387           return timestamp;
4388         }
4389         return timestampFormat.parse(input);
4390       }
4391       //else no slash
4392       return timestampNoSlashFormat.parse(input);
4393     } catch (ParseException pe) {
4394       throw new RuntimeException(errorStart + input);
4395     }
4396   }
4397 
4398   /**
4399    * start of error parsing messages
4400    */
4401   private static final String errorStart = "Invalid timestamp, please use any of the formats: "
4402     + DATE_FORMAT + ", " + TIMESTAMP_FORMAT 
4403     + ", " + DATE_MINUTES_SECONDS_FORMAT + ": ";
4404 
4405   /**
4406    * Convert an object to a byte, allow nulls
4407    * @param input
4408    * @return the boolean object value
4409    */
4410   public static BigDecimal bigDecimalObjectValue(Object input) {
4411     if (input instanceof BigDecimal) {
4412       return (BigDecimal)input;
4413     }
4414     if (isBlank(input)) {
4415       return null;
4416     }
4417     return BigDecimal.valueOf(doubleValue(input));
4418   }
4419 
4420   /**
4421    * Convert an object to a byte, allow nulls
4422    * @param input
4423    * @return the boolean object value
4424    */
4425   public static Byte byteObjectValue(Object input) {
4426     if (input instanceof Byte) {
4427       return (Byte)input;
4428     }
4429     if (isBlank(input)) {
4430       return null;
4431     }
4432     return Byte.valueOf(byteValue(input));
4433   }
4434 
4435   /**
4436    * convert an object to a byte
4437    * @param input
4438    * @return the byte
4439    */
4440   public static byte byteValue(Object input) {
4441     if (input instanceof String) {
4442       String string = (String)input;
4443       return Byte.parseByte(string);
4444     }
4445     if (input instanceof Number) {
4446       return ((Number)input).byteValue();
4447     }
4448     throw new RuntimeException("Cannot convert to byte: " + className(input));
4449   }
4450 
4451   /**
4452    * get the Double value of an object
4453    * 
4454    * @param input
4455    *          is a number or String
4456    * @param allowNullBlank used to default to false, if true, return null if nul inputted 
4457    * 
4458    * @return the Double equivalent
4459    */
4460   public static Double doubleObjectValue(Object input, boolean allowNullBlank) {
4461   
4462     if (input instanceof Double) {
4463       return (Double) input;
4464     } 
4465     
4466     if (allowNullBlank && isBlank(input)) {
4467       return null;
4468     }
4469     
4470     return Double.valueOf(doubleValue(input));
4471   }
4472 
4473   /**
4474    * get the double value of an object
4475    * 
4476    * @param input
4477    *          is a number or String
4478    * 
4479    * @return the double equivalent
4480    */
4481   public static double doubleValue(Object input) {
4482     if (input instanceof String) {
4483       String string = (String)input;
4484       return Double.parseDouble(string);
4485     }
4486     if (input instanceof Number) {
4487       return ((Number)input).doubleValue();
4488     }
4489     throw new RuntimeException("Cannot convert to double: "  + className(input));
4490   }
4491 
4492   /**
4493    * get the double value of an object, do not throw an 
4494    * exception if there is an
4495    * error
4496    * 
4497    * @param input
4498    *          is a number or String
4499    * 
4500    * @return the double equivalent
4501    */
4502   public static double doubleValueNoError(Object input) {
4503     if (input == null || (input instanceof String 
4504         && isBlank((String)input))) {
4505       return NOT_FOUND;
4506     }
4507   
4508     try {
4509       return doubleValue(input);
4510     } catch (Exception e) {
4511       //no need to log here
4512     }
4513   
4514     return NOT_FOUND;
4515   }
4516 
4517   /**
4518    * get the Float value of an object
4519    * 
4520    * @param input
4521    *          is a number or String
4522    * @param allowNullBlank true if allow null or blank
4523    * 
4524    * @return the Float equivalent
4525    */
4526   public static Float floatObjectValue(Object input, boolean allowNullBlank) {
4527   
4528     if (input instanceof Float) {
4529       return (Float) input;
4530     } 
4531   
4532     if (allowNullBlank && isBlank(input)) {
4533       return null;
4534     }
4535     return Float.valueOf(floatValue(input));
4536   }
4537 
4538   /**
4539    * get the float value of an object
4540    * 
4541    * @param input
4542    *          is a number or String
4543    * 
4544    * @return the float equivalent
4545    */
4546   public static float floatValue(Object input) {
4547     if (input instanceof String) {
4548       String string = (String)input;
4549       return Float.parseFloat(string);
4550     }
4551     if (input instanceof Number) {
4552       return ((Number)input).floatValue();
4553     }
4554     throw new RuntimeException("Cannot convert to float: " + className(input));
4555   }
4556 
4557   /**
4558    * get the float value of an object, do not throw an exception if there is an
4559    * error
4560    * 
4561    * @param input
4562    *          is a number or String
4563    * 
4564    * @return the float equivalent
4565    */
4566   public static float floatValueNoError(Object input) {
4567     if (input == null || (input instanceof String 
4568         && isBlank((String)input))) {
4569       return NOT_FOUND;
4570     }
4571     try {
4572       return floatValue(input);
4573     } catch (Exception e) {
4574       e.printStackTrace();
4575     }
4576   
4577     return NOT_FOUND;
4578   }
4579 
4580   /**
4581    * get the Integer value of an object
4582    * 
4583    * @param input
4584    *          is a number or String
4585    * @param allowNullBlank true if convert null or blank to null
4586    * 
4587    * @return the Integer equivalent
4588    */
4589   public static Integer intObjectValue(Object input, boolean allowNullBlank) {
4590   
4591     if (input instanceof Integer) {
4592       return (Integer) input;
4593     } 
4594   
4595     if (allowNullBlank && isBlank(input)) {
4596       return null;
4597     }
4598     
4599     return Integer.valueOf(intValue(input));
4600   }
4601 
4602   /**
4603    * convert an object to a int
4604    * @param input
4605    * @return the number
4606    */
4607   public static int intValue(Object input) {
4608     if (input instanceof String) {
4609       String string = (String)input;
4610       return Integer.parseInt(string);
4611     }
4612     if (input instanceof Number) {
4613       return ((Number)input).intValue();
4614     }
4615     if (false) {
4616       if (input == null) {
4617         return 0;
4618       }
4619       if (input instanceof String || isBlank((String)input)) {
4620         return 0;
4621       }
4622     }
4623     
4624     throw new RuntimeException("Cannot convert to int: " + className(input));
4625   }
4626 
4627   /**
4628    * convert an object to a int
4629    * @param input
4630    * @param valueIfNull is if the input is null or empty, return this value
4631    * @return the number
4632    */
4633   public static int intValue(Object input, int valueIfNull) {
4634     if (input == null || "".equals(input)) {
4635       return valueIfNull;
4636     }
4637     return intObjectValue(input, false);
4638   }
4639 
4640   /**
4641    * get the int value of an object, do not throw an exception if there is an
4642    * error
4643    * 
4644    * @param input
4645    *          is a number or String
4646    * 
4647    * @return the int equivalent
4648    */
4649   public static int intValueNoError(Object input) {
4650     if (input == null || (input instanceof String 
4651         && isBlank((String)input))) {
4652       return NOT_FOUND;
4653     }
4654     try {
4655       return intValue(input);
4656     } catch (Exception e) {
4657       //no need to log here
4658     }
4659   
4660     return NOT_FOUND;
4661   }
4662 
4663   /** special number when a number is not found */
4664   public static final int NOT_FOUND = -999999999;
4665 
4666   /**
4667    * The name says it all.
4668    */
4669   public static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
4670 
4671   /**
4672    * get the Long value of an object
4673    * 
4674    * @param input
4675    *          is a number or String
4676    * @param allowNullBlank true if null or blank converts to null
4677    * 
4678    * @return the Long equivalent
4679    */
4680   public static Long longObjectValue(Object input, boolean allowNullBlank) {
4681   
4682     if (input instanceof Long) {
4683       return (Long) input;
4684     } 
4685   
4686     if (allowNullBlank && isBlank(input)) {
4687       return null;
4688     } 
4689     
4690     return Long.valueOf(longValue(input));
4691   }
4692 
4693   /**
4694    * convert an object to a long
4695    * @param input
4696    * @return the number
4697    */
4698   public static long longValue(Object input) {
4699     if (input instanceof String) {
4700       String string = (String)input;
4701       return Long.parseLong(string);
4702     }
4703     if (input instanceof Number) {
4704       return ((Number)input).longValue();
4705     }
4706     throw new RuntimeException("Cannot convert to long: " + className(input));
4707   }
4708 
4709   /**
4710    * convert an object to a long
4711    * @param input
4712    * @param valueIfNull is if the input is null or empty, return this value
4713    * @return the number
4714    */
4715   public static long longValue(Object input, long valueIfNull) {
4716     if (input == null || "".equals(input)) {
4717       return valueIfNull;
4718     }
4719     return longObjectValue(input, false);
4720   }
4721 
4722   /**
4723    * get the long value of an object, do not throw an exception if there is an
4724    * error
4725    * 
4726    * @param input
4727    *          is a number or String
4728    * 
4729    * @return the long equivalent
4730    */
4731   public static long longValueNoError(Object input) {
4732     if (input == null || (input instanceof String 
4733         && isBlank((String)input))) {
4734       return NOT_FOUND;
4735     }
4736     try {
4737       return longValue(input);
4738     } catch (Exception e) {
4739       //no need to log here
4740     }
4741   
4742     return NOT_FOUND;
4743   }
4744 
4745   /**
4746    * get the Short value of an object.  converts null or blank to null
4747    * 
4748    * @param input
4749    *          is a number or String
4750    * 
4751    * @return the Long equivalent
4752    */
4753   public static Short shortObjectValue(Object input) {
4754   
4755     if (input instanceof Short) {
4756       return (Short) input;
4757     }
4758   
4759     if (isBlank(input)) {
4760       return null;
4761     } 
4762     
4763     return Short.valueOf(shortValue(input));
4764   }
4765 
4766   /**
4767    * convert an object to a short
4768    * @param input
4769    * @return the number
4770    */
4771   public static short shortValue(Object input) {
4772     if (input instanceof String) {
4773       String string = (String)input;
4774       return Short.parseShort(string);
4775     }
4776     if (input instanceof Number) {
4777       return ((Number)input).shortValue();
4778     }
4779     throw new RuntimeException("Cannot convert to short: " + className(input));
4780   }
4781 
4782   /**
4783    * get the Character wrapper value for the input
4784    * @param input allow null, return null
4785    * @return the Character object wrapper
4786    */
4787   public static Character charObjectValue(Object input) {
4788     if (input instanceof Character) {
4789       return (Character) input;
4790     }
4791     if (isBlank(input)) {
4792       return null;
4793     }
4794     return new Character(charValue(input));
4795   }
4796 
4797   /**
4798    * convert an object to a char
4799    * @param input
4800    * @return the number
4801    */
4802   public static char charValue(Object input) {
4803     if (input instanceof Character) {
4804       return ((Character) input).charValue();
4805     }
4806     //if string length 1, thats ok
4807     if (input instanceof String) {
4808       String inputString = (String) input;
4809       if (inputString.length() == 1) {
4810         return inputString.charAt(0);
4811       }
4812     }
4813     throw new RuntimeException("Cannot convert to char: "
4814         + (input == null ? null : (input.getClass() + ", " + input)));
4815   }
4816 
4817   /**
4818    * Create the parent directories for a file if they do not already exist
4819    * @param file
4820    */
4821   public static void createParentDirectories(File file) {
4822     if (!file.getParentFile().exists()) {
4823       if (!file.getParentFile().mkdirs()) {
4824         throw new RuntimeException("Could not create directory : " + file.getParentFile());
4825       }
4826     }
4827   }
4828 
4829   /**
4830    * save a string into a file, file does not have to exist
4831    * 
4832    * @param file
4833    *          is the file to save to
4834    * @param contents
4835    *          is the contents of the file
4836    */
4837   public static void saveStringIntoFile(File file, String contents) {
4838     try {
4839       writeStringToFile(file, contents, "UTF-8");
4840     } catch (IOException ioe) {
4841       throw new RuntimeException(ioe);
4842     }
4843   }
4844 
4845   /**
4846    * save a string into a file, file does not have to exist
4847    * 
4848    * @param file
4849    *          is the file to save to
4850    * @param contents
4851    *          is the contents of the file
4852    * @param onlyIfDifferentContents true if only saving due to different contents
4853    * @param ignoreWhitespace true to ignore whitespace
4854    * @return true if contents were saved (thus different if param set)
4855    */
4856   public static boolean saveStringIntoFile(File file, String contents, 
4857       boolean onlyIfDifferentContents, boolean ignoreWhitespace) {
4858     if (onlyIfDifferentContents && file.exists()) {
4859       String fileContents = readFileIntoString(file);
4860       String compressedContents = contents;
4861       if (ignoreWhitespace) {
4862         compressedContents = replaceWhitespaceWithSpace(compressedContents);
4863         fileContents = replaceWhitespaceWithSpace(fileContents);
4864       }
4865       
4866       //they are the same, dont worry about it
4867       if (equals(fileContents, compressedContents)) {
4868         return false;
4869       }
4870   
4871     }
4872     saveStringIntoFile(file, contents);
4873     return true;
4874   }
4875 
4876   /**
4877    * <p>
4878    * Writes data to a file. The file will be created if it does not exist.
4879    * </p>
4880    * <p>
4881    * There is no readFileToString method without encoding parameter because
4882    * the default encoding can differ between platforms and therefore results
4883    * in inconsistent results.
4884    * </p>
4885    *
4886    * @param file the file to write.
4887    * @param data The content to write to the file.
4888    * @param encoding encoding to use
4889    * @throws IOException in case of an I/O error
4890    * @throws UnsupportedEncodingException if the encoding is not supported
4891    *   by the VM
4892    */
4893   public static void writeStringToFile(File file, String data, String encoding)
4894       throws IOException {
4895     OutputStream out = new java.io.FileOutputStream(file);
4896     try {
4897       out.write(data.getBytes(encoding));
4898     } finally {
4899       closeQuietly(out);
4900     }
4901   }
4902 
4903   /**
4904    * @param file
4905    *          is the file to read into a string
4906    * 
4907    * @return String
4908    */
4909   public static String readFileIntoString(File file) {
4910   
4911     if (file == null) {
4912       return null;
4913     }
4914     try {
4915       return readFileToString(file, "UTF-8");
4916     } catch (IOException ioe) {
4917       throw new RuntimeException(ioe);
4918     }
4919   }
4920 
4921   /**
4922    * @param resourceName is the string resource from classpath to read (e.g. grouper.properties)
4923    * @param allowNull is true if its ok if the resource is null or if it is not found or blank or whatever.
4924    * 
4925    * @return String or null if allowed or RuntimeException if not allowed
4926    */
4927   public static String readResourceIntoString(String resourceName, boolean allowNull) {
4928     if (isBlank(resourceName)) {
4929       if (allowNull) {
4930         return null;
4931       }
4932       throw new RuntimeException("Resource name is blank");
4933     }
4934     URL url = computeUrl(resourceName, allowNull);
4935 
4936     //this is ok
4937     if (url == null && allowNull) {
4938       return null;
4939     }
4940     
4941     InputStream inputStream = null;
4942     StringWriter stringWriter = new StringWriter();
4943     try {
4944       inputStream = url.openStream();
4945       copy(inputStream, stringWriter, "UTF-8");
4946     } catch (IOException ioe) {
4947       throw new RuntimeException("Error reading resource: '" + resourceName + "'", ioe);
4948     } finally {
4949       closeQuietly(inputStream);
4950       closeQuietly(stringWriter);
4951     }
4952     return stringWriter.toString();
4953   }
4954 
4955   /**
4956    * read resource into string
4957    * @param resourceName
4958    * @param classInJar if not null, then look for the jar where this file is, and look in the same dir
4959    * @return the properties or null if not exist
4960    */
4961   public static String readResourceIntoString(String resourceName, Class<?> classInJar) {
4962 
4963     try {
4964       return readResourceIntoString(resourceName, false);
4965     } catch (Exception e) {
4966       //try from jar location
4967     }
4968   
4969     //lets look next to jar
4970     File jarFile = classInJar == null ? null : jarFile(classInJar);
4971     File parentDir = jarFile == null ? null : jarFile.getParentFile();
4972     String fileName = parentDir == null ? null 
4973         : (stripLastSlashIfExists(fileCanonicalPath(parentDir)) + File.separator + resourceName);
4974     File configFile = fileName == null ? null 
4975         : new File(fileName);
4976 
4977     return readFileIntoString(configFile);
4978   }
4979 
4980   /**
4981    * <p>
4982    * Reads the contents of a file into a String.
4983    * </p>
4984    * <p>
4985    * There is no readFileToString method without encoding parameter because
4986    * the default encoding can differ between platforms and therefore results
4987    * in inconsistent results.
4988    * </p>
4989    *
4990    * @param file the file to read.
4991    * @param encoding the encoding to use
4992    * @return The file contents or null if read failed.
4993    * @throws IOException in case of an I/O error
4994    */
4995   public static String readFileToString(File file, String encoding) throws IOException {
4996     InputStream in = new java.io.FileInputStream(file);
4997     try {
4998       return toString(in, encoding);
4999     } finally {
5000       closeQuietly(in);
5001     }
5002   }
5003 
5004   /**
5005    * replace all whitespace with space
5006    * @param input
5007    * @return the string
5008    */
5009   public static String replaceWhitespaceWithSpace(String input) {
5010     if (input == null) {
5011       return input;
5012     }
5013     return input.replaceAll("\\s+", " ");
5014   }
5015 
5016   /**
5017    * Unconditionally close an <code>InputStream</code>.
5018    * Equivalent to {@link InputStream#close()}, except any exceptions will be ignored.
5019    * @param input A (possibly null) InputStream
5020    */
5021   public static void closeQuietly(InputStream input) {
5022     if (input == null) {
5023       return;
5024     }
5025   
5026     try {
5027       input.close();
5028     } catch (IOException ioe) {
5029     }
5030   }
5031 
5032   /**
5033    * Unconditionally close an <code>OutputStream</code>.
5034    * Equivalent to {@link OutputStream#close()}, except any exceptions will be ignored.
5035    * @param output A (possibly null) OutputStream
5036    */
5037   public static void closeQuietly(OutputStream output) {
5038     if (output == null) {
5039       return;
5040     }
5041   
5042     try {
5043       output.close();
5044     } catch (IOException ioe) {
5045     }
5046   }
5047 
5048   /**
5049    * Unconditionally close an <code>Reader</code>.
5050    * Equivalent to {@link Reader#close()}, except any exceptions will be ignored.
5051    *
5052    * @param input A (possibly null) Reader
5053    */
5054   public static void closeQuietly(Reader input) {
5055     if (input == null) {
5056       return;
5057     }
5058   
5059     try {
5060       input.close();
5061     } catch (IOException ioe) {
5062     }
5063   }
5064 
5065   /**
5066    * close a writer quietly
5067    * @param writer
5068    */
5069   public static void closeQuietly(Writer writer) {
5070     if (writer != null) {
5071       try {
5072         writer.close();
5073       } catch (IOException e) {
5074         //swallow, its ok
5075       }
5076     }
5077   }
5078 
5079   /**
5080    * Get the contents of an <code>InputStream</code> as a String.
5081    * @param input the <code>InputStream</code> to read from
5082    * @param encoding The name of a supported character encoding. See the
5083    *   <a href="http://www.iana.org/assignments/character-sets">IANA
5084    *   Charset Registry</a> for a list of valid encoding types.
5085    * @return the requested <code>String</code>
5086    * @throws IOException In case of an I/O problem
5087    */
5088   public static String toString(InputStream input, String encoding) throws IOException {
5089     StringWriter sw = new StringWriter();
5090     copy(input, sw, encoding);
5091     return sw.toString();
5092   }
5093 
5094   /**
5095    * Copy and convert bytes from an <code>InputStream</code> to chars on a
5096    * <code>Writer</code>, using the specified encoding.
5097    * @param input the <code>InputStream</code> to read from
5098    * @param output the <code>Writer</code> to write to
5099    * @param encoding The name of a supported character encoding. See the
5100    * <a href="http://www.iana.org/assignments/character-sets">IANA
5101    * Charset Registry</a> for a list of valid encoding types.
5102    * @throws IOException In case of an I/O problem
5103    */
5104   public static void copy(InputStream input, Writer output, String encoding)
5105       throws IOException {
5106     InputStreamReader in = new InputStreamReader(input, encoding);
5107     copy(in, output);
5108   }
5109 
5110   /**
5111    * Copy chars from a <code>Reader</code> to a <code>Writer</code>.
5112    * @param input the <code>Reader</code> to read from
5113    * @param output the <code>Writer</code> to write to
5114    * @return the number of characters copied
5115    * @throws IOException In case of an I/O problem
5116    */
5117   public static int copy(Reader input, Writer output) throws IOException {
5118     char[] buffer = new char[DEFAULT_BUFFER_SIZE];
5119     int count = 0;
5120     int n = 0;
5121     while (-1 != (n = input.read(buffer))) {
5122       output.write(buffer, 0, n);
5123       count += n;
5124     }
5125     return count;
5126   }
5127 
5128   /**
5129    * this method takes a long (less than 62) and converts it to a 1 character
5130    * string (a-z, A-Z, 0-9)
5131    * 
5132    * @param theLong
5133    *          is the long (less than 62) to convert to a 1 character string
5134    * 
5135    * @return a one character string
5136    */
5137   public static String convertLongToChar(long theLong) {
5138     if ((theLong < 0) || (theLong >= 62)) {
5139       throw new RuntimeException("convertLongToChar() "
5140           + " invalid input (not >=0 && <62: " + theLong);
5141     } else if (theLong < 26) {
5142       return "" + (char) ('a' + theLong);
5143     } else if (theLong < 52) {
5144       return "" + (char) ('A' + (theLong - 26));
5145     } else {
5146       return "" + (char) ('0' + (theLong - 52));
5147     }
5148   }
5149 
5150   /**
5151    * this method takes a long (less than 36) and converts it to a 1 character
5152    * string (A-Z, 0-9)
5153    * 
5154    * @param theLong
5155    *          is the long (less than 36) to convert to a 1 character string
5156    * 
5157    * @return a one character string
5158    */
5159   public static String convertLongToCharSmall(long theLong) {
5160     if ((theLong < 0) || (theLong >= 36)) {
5161       throw new RuntimeException("convertLongToCharSmall() "
5162           + " invalid input (not >=0 && <36: " + theLong);
5163     } else if (theLong < 26) {
5164       return "" + (char) ('A' + theLong);
5165     } else {
5166       return "" + (char) ('0' + (theLong - 26));
5167     }
5168   }
5169 
5170   /**
5171    * convert a long to a string by converting it to base 62 (26 lower, 26 upper,
5172    * 10 digits)
5173    * 
5174    * @param theLong
5175    *          is the long to convert
5176    * 
5177    * @return the String conversion of this
5178    */
5179   public static String convertLongToString(long theLong) {
5180     long quotient = theLong / 62;
5181     long remainder = theLong % 62;
5182   
5183     if (quotient == 0) {
5184       return convertLongToChar(remainder);
5185     }
5186     StringBuffer result = new StringBuffer();
5187     result.append(convertLongToString(quotient));
5188     result.append(convertLongToChar(remainder));
5189   
5190     return result.toString();
5191   }
5192 
5193   /**
5194    * convert a long to a string by converting it to base 36 (26 upper, 10
5195    * digits)
5196    * 
5197    * @param theLong
5198    *          is the long to convert
5199    * 
5200    * @return the String conversion of this
5201    */
5202   public static String convertLongToStringSmall(long theLong) {
5203     long quotient = theLong / 36;
5204     long remainder = theLong % 36;
5205   
5206     if (quotient == 0) {
5207       return convertLongToCharSmall(remainder);
5208     }
5209     StringBuffer result = new StringBuffer();
5210     result.append(convertLongToStringSmall(quotient));
5211     result.append(convertLongToCharSmall(remainder));
5212   
5213     return result.toString();
5214   }
5215 
5216   /**
5217    * increment a character (A-Z then 0-9)
5218    * 
5219    * @param theChar
5220    * 
5221    * @return the value
5222    */
5223   public static char incrementChar(char theChar) {
5224     if (theChar == 'Z') {
5225       return '0';
5226     }
5227   
5228     if (theChar == '9') {
5229       return 'A';
5230     }
5231   
5232     return ++theChar;
5233   }
5234 
5235   /**
5236    * Increment a string with A-Z and 0-9 (no lower case so case insensitive apps
5237    * like windows IE will still work)
5238    * 
5239    * @param string
5240    * 
5241    * @return the value
5242    */
5243   public static char[] incrementStringInt(char[] string) {
5244     if (string == null) {
5245       return string;
5246     }
5247   
5248     //loop through the string backwards
5249     int i = 0;
5250   
5251     for (i = string.length - 1; i >= 0; i--) {
5252       char inc = string[i];
5253       inc = incrementChar(inc);
5254       string[i] = inc;
5255   
5256       if (inc != 'A') {
5257         break;
5258       }
5259     }
5260   
5261     //if we are at 0, then it means we hit AAAAAAA (or more)
5262     if (i < 0) {
5263       return ("A" + new String(string)).toCharArray();
5264     }
5265   
5266     return string;
5267   }
5268 
5269   /**
5270    * read properties from a resource, dont modify the properties returned since they are cached
5271    * @param resourceName
5272    * @return the properties
5273    */
5274   public synchronized static Properties propertiesFromResourceName(String resourceName) {
5275     return propertiesFromResourceName(resourceName, true, true, null, null);
5276   }
5277 
5278   /**
5279    * clear properties cache (e.g. for testing)
5280    */
5281   public static void propertiesCacheClear() {
5282     resourcePropertiesCache.clear();
5283   }
5284   
5285   /**
5286    * read properties from file
5287    * @param file
5288    * @return properties
5289    */
5290   public static Properties propertiesFromFile(File file) {
5291     Properties properties = new Properties();
5292     FileInputStream fileInputStream = null;
5293     try {
5294       
5295       fileInputStream = new FileInputStream(file);
5296       properties.load(fileInputStream);
5297       
5298     } catch (IOException ioe) {
5299       throw new RuntimeException("Problem reading file into properties: " + file.getAbsolutePath());
5300     } finally {
5301       closeQuietly(fileInputStream);
5302     }
5303     return properties;
5304   }
5305   
5306   /**
5307    * read properties from a resource, dont modify the properties returned since they are cached
5308    * @param resourceName
5309    * @param useCache 
5310    * @param exceptionIfNotExist 
5311    * @param classInJar if not null, then look for the jar where this file is, and look in the same dir
5312    * @param callingLog 
5313    * @return the properties or null if not exist
5314    */
5315   public synchronized static Properties propertiesFromResourceName(String resourceName, boolean useCache, 
5316       boolean exceptionIfNotExist, Class<?> classInJar, StringBuilder callingLog) {
5317 
5318     Properties properties = resourcePropertiesCache.get(resourceName);
5319     
5320     if (!useCache || !resourcePropertiesCache.containsKey(resourceName)) {
5321   
5322       properties = new Properties();
5323 
5324       boolean success = false;
5325       
5326       URL url = computeUrl(resourceName, true);
5327       InputStream inputStream = null;
5328       try {
5329         inputStream = url.openStream();
5330         properties.load(inputStream);
5331         success = true;
5332         String theLog = "Reading resource: " + resourceName + ", from: " + url.toURI();
5333         if (callingLog != null) {
5334           callingLog.append(theLog);
5335         }
5336         if (GrouperClientLog.debugToConsoleByFlag()) {
5337           System.err.println(theLog);
5338         }
5339 
5340       } catch (Exception e) {
5341         
5342         //clear out just in case
5343         properties.clear();
5344 
5345         //lets look next to jar
5346         File jarFile = classInJar == null ? null : jarFile(classInJar);
5347         File parentDir = jarFile == null ? null : jarFile.getParentFile();
5348         String fileName = parentDir == null ? null 
5349             : (stripLastSlashIfExists(fileCanonicalPath(parentDir)) + File.separator + resourceName);
5350         File configFile = fileName == null ? null 
5351             : new File(fileName);
5352 
5353         try {
5354           //looks like we have a match
5355           if (configFile != null && configFile.exists() && configFile.isFile()) {
5356             inputStream = new FileInputStream(configFile);
5357             properties.load(inputStream);
5358             success = true;
5359             String theLog = "Reading resource: " + resourceName + ", from: " + fileCanonicalPath(configFile);
5360             if (callingLog != null) {
5361               callingLog.append(theLog);
5362             }
5363             if (GrouperClientLog.debugToConsoleByFlag()) {
5364               System.err.println(theLog);
5365             }
5366           }
5367           
5368         } catch (Exception e2) {
5369         }
5370         if (!success) {
5371           properties = null;
5372           if (exceptionIfNotExist) {
5373             throw new RuntimeException("Problem with resource: '" + resourceName + "'", e);
5374           }
5375         }
5376       } finally {
5377         closeQuietly(inputStream);
5378         
5379         if (useCache && properties != null && properties.size() > 0) {
5380           resourcePropertiesCache.put(resourceName, properties);
5381         }
5382       }
5383     }
5384     
5385     return properties;
5386   }
5387 
5388   /**
5389    * do a case-insensitive matching
5390    * @param theEnumClass class of the enum
5391    * @param <E> generic type
5392    * 
5393    * @param string
5394    * @param exceptionOnNotFound true if exception should be thrown on not found
5395    * @return the enum or null or exception if not found
5396    * @throws RuntimeException if there is a problem
5397    */
5398   public static <E extends Enum<?>> E enumValueOfIgnoreCase(Class<E> theEnumClass, String string, 
5399       boolean exceptionOnNotFound) throws RuntimeException {
5400     
5401     if (!exceptionOnNotFound && isBlank(string)) {
5402       return null;
5403     }
5404     for (E e : theEnumClass.getEnumConstants()) {
5405       if (equalsIgnoreCase(string, e.name())) {
5406         return e;
5407       }
5408     }
5409     StringBuilder error = new StringBuilder(
5410         "Cant find " + theEnumClass.getSimpleName() + " from string: '").append(string);
5411     error.append("', expecting one of: ");
5412     for (E e : theEnumClass.getEnumConstants()) {
5413       error.append(e.name()).append(", ");
5414     }
5415     throw new RuntimeException(error.toString());
5416   
5417   }
5418 
5419 
5420   /**
5421    * this assumes the property exists, and is a simple property
5422    * @param object
5423    * @param property
5424    * @return the value
5425    */
5426   public static Object propertyValue(Object object, String property)  {
5427     Method getter = getter(object.getClass(), property, true, true);
5428     Object result = invokeMethod(getter, object);
5429     return result;
5430   }
5431 
5432   /**
5433    * get a value (trimmed to e) from a property file
5434    * @param properties
5435    * @param key
5436    * @return the property value
5437    */
5438   public static String propertiesValue(Properties properties, String key) {
5439     return propertiesValue(properties, null, key);
5440   }
5441   
5442   /**
5443    * get a value (trimmed to e) from a property file
5444    * @param properties
5445    * @param overrideMap for testing, to override some properties values
5446    * @param key
5447    * @return the property value
5448    */
5449   public static String propertiesValue(Properties properties, Map<String, String> overrideMap, String key) {
5450     return propertiesValue(properties, overrideMap, null, key);
5451   }
5452 
5453   /**
5454    * get a value (trimmed to e) from a property file
5455    * @param properties
5456    * @param overrideMap for testing or threadlocal, to override some properties values
5457    * @param overrideMap2 for testing, to provide some properties values
5458    * @param key
5459    * @return the property value
5460    */
5461   public static String propertiesValue(Properties properties, Map<String, String> overrideMap, Map<String, String> overrideMap2, String key) {
5462     String value = overrideMap == null ? null : overrideMap.get(key);
5463     if (isBlank(value)) {
5464       value = overrideMap2 == null ? null : overrideMap2.get(key);
5465     }
5466     if (isBlank(value)) {
5467       value = properties.getProperty(key);
5468     }
5469     value = trim(value);
5470     value = substituteCommonVars(value);
5471     return value;
5472   }
5473   
5474   /**
5475    * substitute common vars like $space$ and $newline$
5476    * @param string
5477    * @return the string
5478    */
5479   public static String substituteCommonVars(String string) {
5480     if (string == null) {
5481       return string;
5482     }
5483     //short circuit
5484     if (string.indexOf('$') < 0) {
5485       return string;
5486     }
5487     //might have $space$
5488     string = replace(string, "$space$", " ");
5489     
5490     //note, at some point we could be OS specific
5491     string = replace(string, "$newline$", "\n"); 
5492     return string;
5493   }
5494   
5495   /**
5496    * get a boolean property, or the default if cant find
5497    * @param properties
5498    * @param propertyName
5499    * @param defaultValue 
5500    * @return the boolean
5501    */
5502   public static boolean propertiesValueBoolean(Properties properties,
5503       String propertyName, boolean defaultValue) {
5504     return propertiesValueBoolean(properties, null, propertyName, defaultValue);
5505   }
5506   
5507   /**
5508    * get a boolean property, or the default if cant find.  Validate also with a descriptive exception if problem
5509    * @param resourceName 
5510    * @param properties
5511    * @param overrideMap for testing to override properties
5512    * @param propertyName
5513    * @param defaultValue 
5514    * @param required 
5515    * @return the boolean
5516    */
5517   public static boolean propertiesValueBoolean(String resourceName, Properties properties, 
5518       Map<String, String> overrideMap, String propertyName, boolean defaultValue, boolean required) {
5519     propertyValidateValueBoolean(resourceName, properties, overrideMap, propertyName, required, true);
5520 
5521     Map<String, String> threadLocalMap = propertiesThreadLocalOverrideMap(resourceName);
5522     
5523     return propertiesValueBoolean(properties, threadLocalMap, overrideMap, propertyName, defaultValue);
5524   }
5525   
5526   /**
5527    * get an int property, or the default if cant find.  Validate also with a descriptive exception if problem
5528    * @param resourceName 
5529    * @param properties
5530    * @param overrideMap for testing to override properties
5531    * @param propertyName
5532    * @param defaultValue 
5533    * @param required 
5534    * @return the int
5535    */
5536   public static int propertiesValueInt(String resourceName, Properties properties, 
5537       Map<String, String> overrideMap, String propertyName, int defaultValue, boolean required) {
5538     
5539     propertyValidateValueInt(resourceName, properties, overrideMap, propertyName, required, true);
5540 
5541     Map<String, String> threadLocalMap = propertiesThreadLocalOverrideMap(resourceName);
5542 
5543     return propertiesValueInt(properties, threadLocalMap, overrideMap, propertyName, defaultValue);
5544   }
5545 
5546   /**
5547    * get a boolean property, or the default if cant find.  Validate also with a descriptive exception if problem
5548    * @param resourceName 
5549    * @param properties
5550    * @param overrideMap for threadlocal or testing to override properties
5551    * @param propertyName
5552    * @param required 
5553    * @return the string
5554    */
5555   public static String propertiesValue(String resourceName, Properties properties, 
5556       Map<String, String> overrideMap, String propertyName, boolean required) {
5557 
5558     if (required) {
5559       propertyValidateValueRequired(resourceName, properties, overrideMap, propertyName, true);
5560     }
5561     Map<String, String> threadLocalMap = propertiesThreadLocalOverrideMap(resourceName);
5562 
5563     return propertiesValue(properties, threadLocalMap, overrideMap, propertyName);
5564   }
5565 
5566   /**
5567    * get a int property, or the default if cant find
5568    * @param properties
5569    * @param overrideMap for testing to override properties
5570    * @param propertyName
5571    * @param defaultValue 
5572    * @return the int
5573    */
5574   public static int propertiesValueInt(Properties properties, 
5575       Map<String, String> overrideMap, String propertyName, int defaultValue) {
5576     return propertiesValueInt(properties, overrideMap, null, propertyName, defaultValue);
5577   }
5578 
5579 
5580   /**
5581    * get a int property, or the default if cant find
5582    * @param properties
5583    * @param overrideMap for testing to override properties
5584    * @param overrideMap2 
5585    * @param propertyName
5586    * @param defaultValue 
5587    * @return the int
5588    */
5589   public static int propertiesValueInt(Properties properties, 
5590       Map<String, String> overrideMap, Map<String, String> overrideMap2, String propertyName, int defaultValue) {
5591 
5592     String value = propertiesValue(properties, overrideMap, overrideMap2, propertyName);
5593     if (isBlank(value)) {
5594       return defaultValue;
5595     }
5596 
5597     try {
5598       return intValue(value);
5599     } catch (Exception e) {}
5600     
5601     throw new RuntimeException("Invalid int value: '" + value + "' for property: " + propertyName + " in grouper.properties");
5602 
5603   }
5604   
5605   /**
5606    * get a boolean property, or the default if cant find
5607    * @param properties
5608    * @param overrideMap for testing to override properties
5609    * @param propertyName
5610    * @param defaultValue 
5611    * @return the boolean
5612    */
5613   public static boolean propertiesValueBoolean(Properties properties, 
5614       Map<String, String> overrideMap, String propertyName, boolean defaultValue) {
5615     return propertiesValueBoolean(properties, overrideMap, null, propertyName, defaultValue);
5616   }
5617 
5618   /**
5619    * get a boolean property, or the default if cant find
5620    * @param properties
5621    * @param overrideMap for testing or threadlocal to override properties
5622    * @param overrideMap2 for testing or threadlocal to override properties
5623    * @param propertyName
5624    * @param defaultValue 
5625    * @return the boolean
5626    */
5627   public static boolean propertiesValueBoolean(Properties properties, 
5628       Map<String, String> overrideMap, Map<String, String> overrideMap2, String propertyName, boolean defaultValue) {
5629     
5630       
5631     String value = propertiesValue(properties, overrideMap, overrideMap2, propertyName);
5632     if (isBlank(value)) {
5633       return defaultValue;
5634     }
5635     
5636     if ("true".equalsIgnoreCase(value)) {
5637       return true;
5638     }
5639     if ("false".equalsIgnoreCase(value)) {
5640       return false;
5641     }
5642     if ("t".equalsIgnoreCase(value)) {
5643       return true;
5644     }
5645     if ("f".equalsIgnoreCase(value)) {
5646       return false;
5647     }
5648     throw new RuntimeException("Invalid boolean value: '" + value + "' for property: " + propertyName + " in properties file");
5649 
5650   }
5651   
5652   /**
5653    * close a connection null safe and dont throw exception
5654    * @param connection
5655    */
5656   public static void closeQuietly(Connection connection) {
5657     if (connection != null) {
5658       try {
5659         connection.close();
5660       } catch (Exception e) {
5661         //ignore
5662       }
5663     }
5664   }
5665 
5666   /**
5667    * close a statement null safe and dont throw exception
5668    * @param statement
5669    */
5670   public static void closeQuietly(Statement statement) {
5671     if (statement != null) {
5672       try {
5673         statement.close();
5674       } catch (Exception e) {
5675         //ignore
5676       }
5677     }
5678   }
5679 
5680   /**
5681    * close a resultSet null safe and dont throw exception
5682    * @param resultSet
5683    */
5684   public static void closeQuietly(ResultSet resultSet) {
5685     if (resultSet != null) {
5686       try {
5687         resultSet.close();
5688       } catch (Exception e) {
5689         //ignore
5690       }
5691     }
5692   }
5693 
5694   /** cache the hostname, it wont change */
5695   private static String hostname = null;
5696 
5697   /**
5698    * get the hostname of this machine
5699    * @return the hostname
5700    */
5701   public static String hostname() {
5702   
5703     if (isBlank(hostname)) {
5704   
5705       //get the hostname
5706       hostname = "unknown";
5707       try {
5708         InetAddress addr = InetAddress.getLocalHost();
5709   
5710         // Get hostname
5711         hostname = addr.getHostName();
5712       } catch (Exception e) {
5713         System.err.println("Cant find servers hostname: ");
5714         e.printStackTrace();
5715       }
5716     }
5717   
5718     return hostname;
5719   }
5720 
5721   /**
5722    * is ascii char
5723    * @param input
5724    * @return true if ascii
5725    */
5726   public static boolean isAscii(char input) {
5727     return input < 128;
5728   }
5729 
5730   /**
5731    * find the length of ascii chars (non ascii are counted as two)
5732    * @param input
5733    * @return the length of ascii chars
5734    */
5735   public static int lengthAscii(String input) {
5736     if (input == null) {
5737       return 0;
5738     }
5739     //see what real length is
5740     int utfLength = input.length();
5741     //count how many non asciis
5742     int extras = 0;
5743     for (int i=0;i<utfLength;i++) {
5744       //keep count of non ascii chars
5745       if (!isAscii(input.charAt(i))) {
5746         extras++;
5747       }
5748     }
5749     return utfLength + extras;
5750   }
5751 
5752   /**
5753    * rollback a connection quietly
5754    * @param connection
5755    */
5756   public static void rollbackQuietly(Connection connection) {
5757     if (connection != null) {
5758       try {
5759         connection.rollback();
5760       } catch (Exception e) {
5761         //ignore
5762       }
5763     }
5764   }
5765 
5766   /**
5767    * find the length of ascii chars (non ascii are counted as two)
5768    * @param input is the string to operate on
5769    * @param requiredLength length we need the string to be
5770    * @return the length of ascii chars
5771    */
5772   public static String truncateAscii(String input, int requiredLength) {
5773     if (input == null) {
5774       return input;
5775     }
5776     //see what real length is
5777     int utfLength = input.length();
5778     
5779     //see if not worth checking
5780     if (utfLength * 2 < requiredLength) {
5781       return input;
5782     }
5783     
5784     //count how many non asciis
5785     int asciiLength = 0;
5786     for (int i=0;i<utfLength;i++) {
5787       
5788       asciiLength++;
5789       
5790       //keep count of non ascii chars
5791       if (!isAscii(input.charAt(i))) {
5792         asciiLength++;
5793       }
5794       
5795       //see if we are over 
5796       if (asciiLength > requiredLength) {
5797         //do not include the current char
5798         return input.substring(0,i);
5799       }
5800     }
5801     //must have fit
5802     return input;
5803   }
5804   
5805   /**
5806    * if the input is a file, read string from file.  if not, or if disabled from grouper.properties, return the input
5807    * @param in
5808    * @param disableExternalFileLookup 
5809    * @return the result
5810    */
5811   public static String readFromFileIfFile(String in, boolean disableExternalFileLookup) {
5812     
5813     String theIn = in;
5814     //convert both slashes to file slashes
5815     if (File.separatorChar == '/') {
5816       theIn = replace(theIn, "\\", "/");
5817     } else {
5818       theIn = replace(theIn, "/", "\\");
5819     }
5820     
5821     //see if it is a file reference
5822     if (theIn.indexOf(File.separatorChar) != -1 && !disableExternalFileLookup) {
5823       //read the contents of the file into a string
5824       theIn = readFileIntoString(new File(theIn));
5825       return theIn;
5826     }
5827     return in;
5828   
5829   }
5830 
5831   /**
5832    * Create directories, throw exception if not possible.
5833    * This is will be ok if the directory already exists (will not delete it)
5834    * @param dir
5835    */
5836   public static void mkdirs(File dir) {
5837     if (!dir.exists()) {
5838       if (!dir.mkdirs()) {
5839         throw new RuntimeException("Could not create directory : " + dir.getParentFile());
5840       }
5841       return;
5842     }
5843     if (!dir.isDirectory()) {
5844       throw new RuntimeException("Should be a directory but is not: " + dir);
5845     }
5846   }
5847   
5848 
5849   /**
5850    * null safe string compare
5851    * @param first
5852    * @param second
5853    * @return true if equal
5854    */
5855   public static boolean equals(String first, String second) {
5856     if (first == second) {
5857       return true;
5858     }
5859     if (first == null || second == null) {
5860       return false;
5861     }
5862     return first.equals(second);
5863   }
5864 
5865   /**
5866    * <p>Checks if a String is whitespace, empty ("") or null.</p>
5867    *
5868    * <pre>
5869    * isBlank(null)      = true
5870    * isBlank("")        = true
5871    * isBlank(" ")       = true
5872    * isBlank("bob")     = false
5873    * isBlank("  bob  ") = false
5874    * </pre>
5875    *
5876    * @param str  the String to check, may be null
5877    * @return <code>true</code> if the String is null, empty or whitespace
5878    * @since 2.0
5879    */
5880   public static boolean isBlank(String str) {
5881     int strLen;
5882     if (str == null || (strLen = str.length()) == 0) {
5883       return true;
5884     }
5885     for (int i = 0; i < strLen; i++) {
5886       if ((Character.isWhitespace(str.charAt(i)) == false)) {
5887         return false;
5888       }
5889     }
5890     return true;
5891   }
5892 
5893   /**
5894    * 
5895    * @param str
5896    * @return true if not blank
5897    */
5898   public static boolean isNotBlank(String str) {
5899     return !isBlank(str);
5900   }
5901 
5902   /**
5903    * trim whitespace from string
5904    * @param str
5905    * @return trimmed string
5906    */
5907   public static String trim(String str) {
5908     return str == null ? null : str.trim();
5909   }
5910 
5911   /**
5912    * equalsignorecase
5913    * @param str1
5914    * @param str2
5915    * @return true if the strings are equal ignore case
5916    */
5917   public static boolean equalsIgnoreCase(String str1, String str2) {
5918     return str1 == null ? str2 == null : str1.equalsIgnoreCase(str2);
5919   }
5920 
5921   /**
5922    * trim to empty, convert null to empty
5923    * @param str
5924    * @return trimmed
5925    */
5926   public static String trimToEmpty(String str) {
5927     return str == null ? "" : str.trim();
5928   }
5929 
5930   /**
5931    * <p>Abbreviates a String using ellipses. This will turn
5932    * "Now is the time for all good men" into "Now is the time for..."</p>
5933    *
5934    * <p>Specifically:
5935    * <ul>
5936    *   <li>If <code>str</code> is less than <code>maxWidth</code> characters
5937    *       long, return it.</li>
5938    *   <li>Else abbreviate it to <code>(substring(str, 0, max-3) + "...")</code>.</li>
5939    *   <li>If <code>maxWidth</code> is less than <code>4</code>, throw an
5940    *       <code>IllegalArgumentException</code>.</li>
5941    *   <li>In no case will it return a String of length greater than
5942    *       <code>maxWidth</code>.</li>
5943    * </ul>
5944    * </p>
5945    *
5946    * <pre>
5947    * StringUtils.abbreviate(null, *)      = null
5948    * StringUtils.abbreviate("", 4)        = ""
5949    * StringUtils.abbreviate("abcdefg", 6) = "abc..."
5950    * StringUtils.abbreviate("abcdefg", 7) = "abcdefg"
5951    * StringUtils.abbreviate("abcdefg", 8) = "abcdefg"
5952    * StringUtils.abbreviate("abcdefg", 4) = "a..."
5953    * StringUtils.abbreviate("abcdefg", 3) = IllegalArgumentException
5954    * </pre>
5955    *
5956    * @param str  the String to check, may be null
5957    * @param maxWidth  maximum length of result String, must be at least 4
5958    * @return abbreviated String, <code>null</code> if null String input
5959    * @throws IllegalArgumentException if the width is too small
5960    * @since 2.0
5961    */
5962   public static String abbreviate(String str, int maxWidth) {
5963     return abbreviate(str, 0, maxWidth);
5964   }
5965 
5966   /**
5967    * <p>Abbreviates a String using ellipses. This will turn
5968    * "Now is the time for all good men" into "...is the time for..."</p>
5969    *
5970    * <p>Works like <code>abbreviate(String, int)</code>, but allows you to specify
5971    * a "left edge" offset.  Note that this left edge is not necessarily going to
5972    * be the leftmost character in the result, or the first character following the
5973    * ellipses, but it will appear somewhere in the result.
5974    *
5975    * <p>In no case will it return a String of length greater than
5976    * <code>maxWidth</code>.</p>
5977    *
5978    * <pre>
5979    * StringUtils.abbreviate(null, *, *)                = null
5980    * StringUtils.abbreviate("", 0, 4)                  = ""
5981    * StringUtils.abbreviate("abcdefghijklmno", -1, 10) = "abcdefg..."
5982    * StringUtils.abbreviate("abcdefghijklmno", 0, 10)  = "abcdefg..."
5983    * StringUtils.abbreviate("abcdefghijklmno", 1, 10)  = "abcdefg..."
5984    * StringUtils.abbreviate("abcdefghijklmno", 4, 10)  = "abcdefg..."
5985    * StringUtils.abbreviate("abcdefghijklmno", 5, 10)  = "...fghi..."
5986    * StringUtils.abbreviate("abcdefghijklmno", 6, 10)  = "...ghij..."
5987    * StringUtils.abbreviate("abcdefghijklmno", 8, 10)  = "...ijklmno"
5988    * StringUtils.abbreviate("abcdefghijklmno", 10, 10) = "...ijklmno"
5989    * StringUtils.abbreviate("abcdefghijklmno", 12, 10) = "...ijklmno"
5990    * StringUtils.abbreviate("abcdefghij", 0, 3)        = IllegalArgumentException
5991    * StringUtils.abbreviate("abcdefghij", 5, 6)        = IllegalArgumentException
5992    * </pre>
5993    *
5994    * @param str  the String to check, may be null
5995    * @param offset  left edge of source String
5996    * @param maxWidth  maximum length of result String, must be at least 4
5997    * @return abbreviated String, <code>null</code> if null String input
5998    * @throws IllegalArgumentException if the width is too small
5999    * @since 2.0
6000    */
6001   public static String abbreviate(String str, int offset, int maxWidth) {
6002     if (str == null) {
6003       return null;
6004     }
6005     if (maxWidth < 4) {
6006       throw new IllegalArgumentException("Minimum abbreviation width is 4");
6007     }
6008     if (str.length() <= maxWidth) {
6009       return str;
6010     }
6011     if (offset > str.length()) {
6012       offset = str.length();
6013     }
6014     if ((str.length() - offset) < (maxWidth - 3)) {
6015       offset = str.length() - (maxWidth - 3);
6016     }
6017     if (offset <= 4) {
6018       return str.substring(0, maxWidth - 3) + "...";
6019     }
6020     if (maxWidth < 7) {
6021       throw new IllegalArgumentException("Minimum abbreviation width with offset is 7");
6022     }
6023     if ((offset + (maxWidth - 3)) < str.length()) {
6024       return "..." + abbreviate(str.substring(offset), maxWidth - 3);
6025     }
6026     return "..." + str.substring(str.length() - (maxWidth - 3));
6027   }
6028 
6029   // Splitting
6030   //-----------------------------------------------------------------------
6031   /**
6032    * <p>Splits the provided text into an array, using whitespace as the
6033    * separator.
6034    * Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
6035    *
6036    * <p>The separator is not included in the returned String array.
6037    * Adjacent separators are treated as one separator.
6038    * For more control over the split use the StrTokenizer class.</p>
6039    *
6040    * <p>A <code>null</code> input String returns <code>null</code>.</p>
6041    *
6042    * <pre>
6043    * StringUtils.split(null)       = null
6044    * StringUtils.split("")         = []
6045    * StringUtils.split("abc def")  = ["abc", "def"]
6046    * StringUtils.split("abc  def") = ["abc", "def"]
6047    * StringUtils.split(" abc ")    = ["abc"]
6048    * </pre>
6049    *
6050    * @param str  the String to parse, may be null
6051    * @return an array of parsed Strings, <code>null</code> if null String input
6052    */
6053   public static String[] split(String str) {
6054     return split(str, null, -1);
6055   }
6056 
6057   /**
6058    * <p>Splits the provided text into an array, separator specified.
6059    * This is an alternative to using StringTokenizer.</p>
6060    *
6061    * <p>The separator is not included in the returned String array.
6062    * Adjacent separators are treated as one separator.
6063    * For more control over the split use the StrTokenizer class.</p>
6064    *
6065    * <p>A <code>null</code> input String returns <code>null</code>.</p>
6066    *
6067    * <pre>
6068    * StringUtils.split(null, *)         = null
6069    * StringUtils.split("", *)           = []
6070    * StringUtils.split("a.b.c", '.')    = ["a", "b", "c"]
6071    * StringUtils.split("a..b.c", '.')   = ["a", "b", "c"]
6072    * StringUtils.split("a:b:c", '.')    = ["a:b:c"]
6073    * StringUtils.split("a\tb\nc", null) = ["a", "b", "c"]
6074    * StringUtils.split("a b c", ' ')    = ["a", "b", "c"]
6075    * </pre>
6076    *
6077    * @param str  the String to parse, may be null
6078    * @param separatorChar  the character used as the delimiter,
6079    *  <code>null</code> splits on whitespace
6080    * @return an array of parsed Strings, <code>null</code> if null String input
6081    * @since 2.0
6082    */
6083   public static String[] split(String str, char separatorChar) {
6084     return splitWorker(str, separatorChar, false);
6085   }
6086 
6087   /**
6088    * <p>Splits the provided text into an array, separators specified.
6089    * This is an alternative to using StringTokenizer.</p>
6090    *
6091    * <p>The separator is not included in the returned String array.
6092    * Adjacent separators are treated as one separator.
6093    * For more control over the split use the StrTokenizer class.</p>
6094    *
6095    * <p>A <code>null</code> input String returns <code>null</code>.
6096    * A <code>null</code> separatorChars splits on whitespace.</p>
6097    *
6098    * <pre>
6099    * StringUtils.split(null, *)         = null
6100    * StringUtils.split("", *)           = []
6101    * StringUtils.split("abc def", null) = ["abc", "def"]
6102    * StringUtils.split("abc def", " ")  = ["abc", "def"]
6103    * StringUtils.split("abc  def", " ") = ["abc", "def"]
6104    * StringUtils.split("ab:cd:ef", ":") = ["ab", "cd", "ef"]
6105    * </pre>
6106    *
6107    * @param str  the String to parse, may be null
6108    * @param separatorChars  the characters used as the delimiters,
6109    *  <code>null</code> splits on whitespace
6110    * @return an array of parsed Strings, <code>null</code> if null String input
6111    */
6112   public static String[] split(String str, String separatorChars) {
6113     return splitWorker(str, separatorChars, -1, false);
6114   }
6115 
6116   /**
6117    * <p>Splits the provided text into an array with a maximum length,
6118    * separators specified.</p>
6119    *
6120    * <p>The separator is not included in the returned String array.
6121    * Adjacent separators are treated as one separator.</p>
6122    *
6123    * <p>A <code>null</code> input String returns <code>null</code>.
6124    * A <code>null</code> separatorChars splits on whitespace.</p>
6125    *
6126    * <p>If more than <code>max</code> delimited substrings are found, the last
6127    * returned string includes all characters after the first <code>max - 1</code>
6128    * returned strings (including separator characters).</p>
6129    *
6130    * <pre>
6131    * StringUtils.split(null, *, *)            = null
6132    * StringUtils.split("", *, *)              = []
6133    * StringUtils.split("ab de fg", null, 0)   = ["ab", "cd", "ef"]
6134    * StringUtils.split("ab   de fg", null, 0) = ["ab", "cd", "ef"]
6135    * StringUtils.split("ab:cd:ef", ":", 0)    = ["ab", "cd", "ef"]
6136    * StringUtils.split("ab:cd:ef", ":", 2)    = ["ab", "cd:ef"]
6137    * </pre>
6138    *
6139    * @param str  the String to parse, may be null
6140    * @param separatorChars  the characters used as the delimiters,
6141    *  <code>null</code> splits on whitespace
6142    * @param max  the maximum number of elements to include in the
6143    *  array. A zero or negative value implies no limit
6144    * @return an array of parsed Strings, <code>null</code> if null String input
6145    */
6146   public static String[] split(String str, String separatorChars, int max) {
6147     return splitWorker(str, separatorChars, max, false);
6148   }
6149 
6150   /**
6151    * <p>Splits the provided text into an array, separator string specified.</p>
6152    *
6153    * <p>The separator(s) will not be included in the returned String array.
6154    * Adjacent separators are treated as one separator.</p>
6155    *
6156    * <p>A <code>null</code> input String returns <code>null</code>.
6157    * A <code>null</code> separator splits on whitespace.</p>
6158    *
6159    * <pre>
6160    * StringUtils.split(null, *)            = null
6161    * StringUtils.split("", *)              = []
6162    * StringUtils.split("ab de fg", null)   = ["ab", "de", "fg"]
6163    * StringUtils.split("ab   de fg", null) = ["ab", "de", "fg"]
6164    * StringUtils.split("ab:cd:ef", ":")    = ["ab", "cd", "ef"]
6165    * StringUtils.split("abstemiouslyaeiouyabstemiously", "aeiouy")  = ["bst", "m", "sl", "bst", "m", "sl"]
6166    * StringUtils.split("abstemiouslyaeiouyabstemiously", "aeiouy")  = ["abstemiously", "abstemiously"]
6167    * </pre>
6168    *
6169    * @param str  the String to parse, may be null
6170    * @param separator  String containing the String to be used as a delimiter,
6171    *  <code>null</code> splits on whitespace
6172    * @return an array of parsed Strings, <code>null</code> if null String was input
6173    */
6174   public static String[] splitByWholeSeparator(String str, String separator) {
6175     return splitByWholeSeparator(str, separator, -1);
6176   }
6177 
6178   /**
6179    * <p>Splits the provided text into an array, separator string specified.
6180    * Returns a maximum of <code>max</code> substrings.</p>
6181    *
6182    * <p>The separator(s) will not be included in the returned String array.
6183    * Adjacent separators are treated as one separator.</p>
6184    *
6185    * <p>A <code>null</code> input String returns <code>null</code>.
6186    * A <code>null</code> separator splits on whitespace.</p>
6187    *
6188    * <pre>
6189    * StringUtils.splitByWholeSeparator(null, *, *)               = null
6190    * StringUtils.splitByWholeSeparator("", *, *)                 = []
6191    * StringUtils.splitByWholeSeparator("ab de fg", null, 0)      = ["ab", "de", "fg"]
6192    * StringUtils.splitByWholeSeparator("ab   de fg", null, 0)    = ["ab", "de", "fg"]
6193    * StringUtils.splitByWholeSeparator("ab:cd:ef", ":", 2)       = ["ab", "cd"]
6194    * StringUtils.splitByWholeSeparator("abstemiouslyaeiouyabstemiously", "aeiouy", 2) = ["bst", "m"]
6195    * StringUtils.splitByWholeSeparator("abstemiouslyaeiouyabstemiously", "aeiouy", 2)  = ["abstemiously", "abstemiously"]
6196    * </pre>
6197    *
6198    * @param str  the String to parse, may be null
6199    * @param separator  String containing the String to be used as a delimiter,
6200    *  <code>null</code> splits on whitespace
6201    * @param max  the maximum number of elements to include in the returned
6202    *  array. A zero or negative value implies no limit.
6203    * @return an array of parsed Strings, <code>null</code> if null String was input
6204    */
6205   @SuppressWarnings("unchecked")
6206   public static String[] splitByWholeSeparator(String str, String separator, int max) {
6207     if (str == null) {
6208       return null;
6209     }
6210 
6211     int len = str.length();
6212 
6213     if (len == 0) {
6214       return EMPTY_STRING_ARRAY;
6215     }
6216 
6217     if ((separator == null) || ("".equals(separator))) {
6218       // Split on whitespace.
6219       return split(str, null, max);
6220     }
6221 
6222     int separatorLength = separator.length();
6223 
6224     ArrayList substrings = new ArrayList();
6225     int numberOfSubstrings = 0;
6226     int beg = 0;
6227     int end = 0;
6228     while (end < len) {
6229       end = str.indexOf(separator, beg);
6230 
6231       if (end > -1) {
6232         if (end > beg) {
6233           numberOfSubstrings += 1;
6234 
6235           if (numberOfSubstrings == max) {
6236             end = len;
6237             substrings.add(str.substring(beg));
6238           } else {
6239             // The following is OK, because String.substring( beg, end ) excludes
6240             // the character at the position 'end'.
6241             substrings.add(str.substring(beg, end));
6242 
6243             // Set the starting point for the next search.
6244             // The following is equivalent to beg = end + (separatorLength - 1) + 1,
6245             // which is the right calculation:
6246             beg = end + separatorLength;
6247           }
6248         } else {
6249           // We found a consecutive occurrence of the separator, so skip it.
6250           beg = end + separatorLength;
6251         }
6252       } else {
6253         // String.substring( beg ) goes from 'beg' to the end of the String.
6254         substrings.add(str.substring(beg));
6255         end = len;
6256       }
6257     }
6258 
6259     return (String[]) substrings.toArray(new String[substrings.size()]);
6260   }
6261 
6262   //-----------------------------------------------------------------------
6263   /**
6264    * <p>Splits the provided text into an array, using whitespace as the
6265    * separator, preserving all tokens, including empty tokens created by 
6266    * adjacent separators. This is an alternative to using StringTokenizer.
6267    * Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
6268    *
6269    * <p>The separator is not included in the returned String array.
6270    * Adjacent separators are treated as separators for empty tokens.
6271    * For more control over the split use the StrTokenizer class.</p>
6272    *
6273    * <p>A <code>null</code> input String returns <code>null</code>.</p>
6274    *
6275    * <pre>
6276    * StringUtils.splitPreserveAllTokens(null)       = null
6277    * StringUtils.splitPreserveAllTokens("")         = []
6278    * StringUtils.splitPreserveAllTokens("abc def")  = ["abc", "def"]
6279    * StringUtils.splitPreserveAllTokens("abc  def") = ["abc", "", "def"]
6280    * StringUtils.splitPreserveAllTokens(" abc ")    = ["", "abc", ""]
6281    * </pre>
6282    *
6283    * @param str  the String to parse, may be <code>null</code>
6284    * @return an array of parsed Strings, <code>null</code> if null String input
6285    * @since 2.1
6286    */
6287   public static String[] splitPreserveAllTokens(String str) {
6288     return splitWorker(str, null, -1, true);
6289   }
6290 
6291   /**
6292    * <p>Splits the provided text into an array, separator specified,
6293    * preserving all tokens, including empty tokens created by adjacent
6294    * separators. This is an alternative to using StringTokenizer.</p>
6295    *
6296    * <p>The separator is not included in the returned String array.
6297    * Adjacent separators are treated as separators for empty tokens.
6298    * For more control over the split use the StrTokenizer class.</p>
6299    *
6300    * <p>A <code>null</code> input String returns <code>null</code>.</p>
6301    *
6302    * <pre>
6303    * StringUtils.splitPreserveAllTokens(null, *)         = null
6304    * StringUtils.splitPreserveAllTokens("", *)           = []
6305    * StringUtils.splitPreserveAllTokens("a.b.c", '.')    = ["a", "b", "c"]
6306    * StringUtils.splitPreserveAllTokens("a..b.c", '.')   = ["a", "b", "c"]
6307    * StringUtils.splitPreserveAllTokens("a:b:c", '.')    = ["a:b:c"]
6308    * StringUtils.splitPreserveAllTokens("a\tb\nc", null) = ["a", "b", "c"]
6309    * StringUtils.splitPreserveAllTokens("a b c", ' ')    = ["a", "b", "c"]
6310    * StringUtils.splitPreserveAllTokens("a b c ", ' ')   = ["a", "b", "c", ""]
6311    * StringUtils.splitPreserveAllTokens("a b c ", ' ')   = ["a", "b", "c", "", ""]
6312    * StringUtils.splitPreserveAllTokens(" a b c", ' ')   = ["", a", "b", "c"]
6313    * StringUtils.splitPreserveAllTokens("  a b c", ' ')  = ["", "", a", "b", "c"]
6314    * StringUtils.splitPreserveAllTokens(" a b c ", ' ')  = ["", a", "b", "c", ""]
6315    * </pre>
6316    *
6317    * @param str  the String to parse, may be <code>null</code>
6318    * @param separatorChar  the character used as the delimiter,
6319    *  <code>null</code> splits on whitespace
6320    * @return an array of parsed Strings, <code>null</code> if null String input
6321    * @since 2.1
6322    */
6323   public static String[] splitPreserveAllTokens(String str, char separatorChar) {
6324     return splitWorker(str, separatorChar, true);
6325   }
6326 
6327   /**
6328    * Performs the logic for the <code>split</code> and 
6329    * <code>splitPreserveAllTokens</code> methods that do not return a
6330    * maximum array length.
6331    *
6332    * @param str  the String to parse, may be <code>null</code>
6333    * @param separatorChar the separate character
6334    * @param preserveAllTokens if <code>true</code>, adjacent separators are
6335    * treated as empty token separators; if <code>false</code>, adjacent
6336    * separators are treated as one separator.
6337    * @return an array of parsed Strings, <code>null</code> if null String input
6338    */
6339   @SuppressWarnings("unchecked")
6340   private static String[] splitWorker(String str, char separatorChar,
6341       boolean preserveAllTokens) {
6342     // Performance tuned for 2.0 (JDK1.4)
6343 
6344     if (str == null) {
6345       return null;
6346     }
6347     int len = str.length();
6348     if (len == 0) {
6349       return EMPTY_STRING_ARRAY;
6350     }
6351     List list = new ArrayList();
6352     int i = 0, start = 0;
6353     boolean match = false;
6354     boolean lastMatch = false;
6355     while (i < len) {
6356       if (str.charAt(i) == separatorChar) {
6357         if (match || preserveAllTokens) {
6358           list.add(str.substring(start, i));
6359           match = false;
6360           lastMatch = true;
6361         }
6362         start = ++i;
6363         continue;
6364       }
6365       lastMatch = false;
6366       match = true;
6367       i++;
6368     }
6369     if (match || (preserveAllTokens && lastMatch)) {
6370       list.add(str.substring(start, i));
6371     }
6372     return (String[]) list.toArray(new String[list.size()]);
6373   }
6374 
6375   /**
6376    * <p>Splits the provided text into an array, separators specified, 
6377    * preserving all tokens, including empty tokens created by adjacent
6378    * separators. This is an alternative to using StringTokenizer.</p>
6379    *
6380    * <p>The separator is not included in the returned String array.
6381    * Adjacent separators are treated as separators for empty tokens.
6382    * For more control over the split use the StrTokenizer class.</p>
6383    *
6384    * <p>A <code>null</code> input String returns <code>null</code>.
6385    * A <code>null</code> separatorChars splits on whitespace.</p>
6386    *
6387    * <pre>
6388    * StringUtils.splitPreserveAllTokens(null, *)           = null
6389    * StringUtils.splitPreserveAllTokens("", *)             = []
6390    * StringUtils.splitPreserveAllTokens("abc def", null)   = ["abc", "def"]
6391    * StringUtils.splitPreserveAllTokens("abc def", " ")    = ["abc", "def"]
6392    * StringUtils.splitPreserveAllTokens("abc  def", " ")   = ["abc", "", def"]
6393    * StringUtils.splitPreserveAllTokens("ab:cd:ef", ":")   = ["ab", "cd", "ef"]
6394    * StringUtils.splitPreserveAllTokens("ab:cd:ef:", ":")  = ["ab", "cd", "ef", ""]
6395    * StringUtils.splitPreserveAllTokens("ab:cd:ef::", ":") = ["ab", "cd", "ef", "", ""]
6396    * StringUtils.splitPreserveAllTokens("ab::cd:ef", ":")  = ["ab", "", cd", "ef"]
6397    * StringUtils.splitPreserveAllTokens(":cd:ef", ":")     = ["", cd", "ef"]
6398    * StringUtils.splitPreserveAllTokens("::cd:ef", ":")    = ["", "", cd", "ef"]
6399    * StringUtils.splitPreserveAllTokens(":cd:ef:", ":")    = ["", cd", "ef", ""]
6400    * </pre>
6401    *
6402    * @param str  the String to parse, may be <code>null</code>
6403    * @param separatorChars  the characters used as the delimiters,
6404    *  <code>null</code> splits on whitespace
6405    * @return an array of parsed Strings, <code>null</code> if null String input
6406    * @since 2.1
6407    */
6408   public static String[] splitPreserveAllTokens(String str, String separatorChars) {
6409     return splitWorker(str, separatorChars, -1, true);
6410   }
6411 
6412   /**
6413    * <p>Splits the provided text into an array with a maximum length,
6414    * separators specified, preserving all tokens, including empty tokens 
6415    * created by adjacent separators.</p>
6416    *
6417    * <p>The separator is not included in the returned String array.
6418    * Adjacent separators are treated as separators for empty tokens.
6419    * Adjacent separators are treated as one separator.</p>
6420    *
6421    * <p>A <code>null</code> input String returns <code>null</code>.
6422    * A <code>null</code> separatorChars splits on whitespace.</p>
6423    *
6424    * <p>If more than <code>max</code> delimited substrings are found, the last
6425    * returned string includes all characters after the first <code>max - 1</code>
6426    * returned strings (including separator characters).</p>
6427    *
6428    * <pre>
6429    * StringUtils.splitPreserveAllTokens(null, *, *)            = null
6430    * StringUtils.splitPreserveAllTokens("", *, *)              = []
6431    * StringUtils.splitPreserveAllTokens("ab de fg", null, 0)   = ["ab", "cd", "ef"]
6432    * StringUtils.splitPreserveAllTokens("ab   de fg", null, 0) = ["ab", "cd", "ef"]
6433    * StringUtils.splitPreserveAllTokens("ab:cd:ef", ":", 0)    = ["ab", "cd", "ef"]
6434    * StringUtils.splitPreserveAllTokens("ab:cd:ef", ":", 2)    = ["ab", "cd:ef"]
6435    * StringUtils.splitPreserveAllTokens("ab   de fg", null, 2) = ["ab", "  de fg"]
6436    * StringUtils.splitPreserveAllTokens("ab   de fg", null, 3) = ["ab", "", " de fg"]
6437    * StringUtils.splitPreserveAllTokens("ab   de fg", null, 4) = ["ab", "", "", "de fg"]
6438    * </pre>
6439    *
6440    * @param str  the String to parse, may be <code>null</code>
6441    * @param separatorChars  the characters used as the delimiters,
6442    *  <code>null</code> splits on whitespace
6443    * @param max  the maximum number of elements to include in the
6444    *  array. A zero or negative value implies no limit
6445    * @return an array of parsed Strings, <code>null</code> if null String input
6446    * @since 2.1
6447    */
6448   public static String[] splitPreserveAllTokens(String str, String separatorChars, int max) {
6449     return splitWorker(str, separatorChars, max, true);
6450   }
6451 
6452   /**
6453    * Performs the logic for the <code>split</code> and 
6454    * <code>splitPreserveAllTokens</code> methods that return a maximum array 
6455    * length.
6456    *
6457    * @param str  the String to parse, may be <code>null</code>
6458    * @param separatorChars the separate character
6459    * @param max  the maximum number of elements to include in the
6460    *  array. A zero or negative value implies no limit.
6461    * @param preserveAllTokens if <code>true</code>, adjacent separators are
6462    * treated as empty token separators; if <code>false</code>, adjacent
6463    * separators are treated as one separator.
6464    * @return an array of parsed Strings, <code>null</code> if null String input
6465    */
6466   @SuppressWarnings("unchecked")
6467   private static String[] splitWorker(String str, String separatorChars, int max,
6468       boolean preserveAllTokens) {
6469     // Performance tuned for 2.0 (JDK1.4)
6470     // Direct code is quicker than StringTokenizer.
6471     // Also, StringTokenizer uses isSpace() not isWhitespace()
6472 
6473     if (str == null) {
6474       return null;
6475     }
6476     int len = str.length();
6477     if (len == 0) {
6478       return EMPTY_STRING_ARRAY;
6479     }
6480     List list = new ArrayList();
6481     int sizePlus1 = 1;
6482     int i = 0, start = 0;
6483     boolean match = false;
6484     boolean lastMatch = false;
6485     if (separatorChars == null) {
6486       // Null separator means use whitespace
6487       while (i < len) {
6488         if (Character.isWhitespace(str.charAt(i))) {
6489           if (match || preserveAllTokens) {
6490             lastMatch = true;
6491             if (sizePlus1++ == max) {
6492               i = len;
6493               lastMatch = false;
6494             }
6495             list.add(str.substring(start, i));
6496             match = false;
6497           }
6498           start = ++i;
6499           continue;
6500         }
6501         lastMatch = false;
6502         match = true;
6503         i++;
6504       }
6505     } else if (separatorChars.length() == 1) {
6506       // Optimise 1 character case
6507       char sep = separatorChars.charAt(0);
6508       while (i < len) {
6509         if (str.charAt(i) == sep) {
6510           if (match || preserveAllTokens) {
6511             lastMatch = true;
6512             if (sizePlus1++ == max) {
6513               i = len;
6514               lastMatch = false;
6515             }
6516             list.add(str.substring(start, i));
6517             match = false;
6518           }
6519           start = ++i;
6520           continue;
6521         }
6522         lastMatch = false;
6523         match = true;
6524         i++;
6525       }
6526     } else {
6527       // standard case
6528       while (i < len) {
6529         if (separatorChars.indexOf(str.charAt(i)) >= 0) {
6530           if (match || preserveAllTokens) {
6531             lastMatch = true;
6532             if (sizePlus1++ == max) {
6533               i = len;
6534               lastMatch = false;
6535             }
6536             list.add(str.substring(start, i));
6537             match = false;
6538           }
6539           start = ++i;
6540           continue;
6541         }
6542         lastMatch = false;
6543         match = true;
6544         i++;
6545       }
6546     }
6547     if (match || (preserveAllTokens && lastMatch)) {
6548       list.add(str.substring(start, i));
6549     }
6550     return (String[]) list.toArray(new String[list.size()]);
6551   }
6552 
6553   // Joining
6554   //-----------------------------------------------------------------------
6555 
6556   /**
6557    * <p>Joins the elements of the provided array into a single String
6558    * containing the provided list of elements.</p>
6559    *
6560    * <p>No separator is added to the joined String.
6561    * Null objects or empty strings within the array are represented by
6562    * empty strings.</p>
6563    *
6564    * <pre>
6565    * StringUtils.join(null)            = null
6566    * StringUtils.join([])              = ""
6567    * StringUtils.join([null])          = ""
6568    * StringUtils.join(["a", "b", "c"]) = "abc"
6569    * StringUtils.join([null, "", "a"]) = "a"
6570    * </pre>
6571    *
6572    * @param array  the array of values to join together, may be null
6573    * @return the joined String, <code>null</code> if null array input
6574    * @since 2.0
6575    */
6576   public static String join(Object[] array) {
6577     return join(array, null);
6578   }
6579 
6580   /**
6581    * <p>Joins the elements of the provided array into a single String
6582    * containing the provided list of elements.</p>
6583    *
6584    * <p>No delimiter is added before or after the list.
6585    * Null objects or empty strings within the array are represented by
6586    * empty strings.</p>
6587    *
6588    * <pre>
6589    * StringUtils.join(null, *)               = null
6590    * StringUtils.join([], *)                 = ""
6591    * StringUtils.join([null], *)             = ""
6592    * StringUtils.join(["a", "b", "c"], ';')  = "a;b;c"
6593    * StringUtils.join(["a", "b", "c"], null) = "abc"
6594    * StringUtils.join([null, "", "a"], ';')  = ";;a"
6595    * </pre>
6596    *
6597    * @param array  the array of values to join together, may be null
6598    * @param separator  the separator character to use
6599    * @return the joined String, <code>null</code> if null array input
6600    * @since 2.0
6601    */
6602   public static String join(Object[] array, char separator) {
6603     if (array == null) {
6604       return null;
6605     }
6606     int arraySize = array.length;
6607     int bufSize = (arraySize == 0 ? 0 : ((array[0] == null ? 16 : array[0].toString()
6608         .length()) + 1)
6609         * arraySize);
6610     StringBuffer buf = new StringBuffer(bufSize);
6611 
6612     for (int i = 0; i < arraySize; i++) {
6613       if (i > 0) {
6614         buf.append(separator);
6615       }
6616       if (array[i] != null) {
6617         buf.append(array[i]);
6618       }
6619     }
6620     return buf.toString();
6621   }
6622 
6623   /**
6624    * <p>Joins the elements of the provided array into a single String
6625    * containing the provided list of elements.</p>
6626    *
6627    * <p>No delimiter is added before or after the list.
6628    * A <code>null</code> separator is the same as an empty String ("").
6629    * Null objects or empty strings within the array are represented by
6630    * empty strings.</p>
6631    *
6632    * <pre>
6633    * StringUtils.join(null, *)                = null
6634    * StringUtils.join([], *)                  = ""
6635    * StringUtils.join([null], *)              = ""
6636    * StringUtils.join(["a", "b", "c"], "--")  = "a--b--c"
6637    * StringUtils.join(["a", "b", "c"], null)  = "abc"
6638    * StringUtils.join(["a", "b", "c"], "")    = "abc"
6639    * StringUtils.join([null, "", "a"], ',')   = ",,a"
6640    * </pre>
6641    *
6642    * @param array  the array of values to join together, may be null
6643    * @param separator  the separator character to use, null treated as ""
6644    * @return the joined String, <code>null</code> if null array input
6645    */
6646   public static String join(Object[] array, String separator) {
6647     if (array == null) {
6648       return null;
6649     }
6650     if (separator == null) {
6651       separator = "";
6652     }
6653     int arraySize = array.length;
6654 
6655     // ArraySize ==  0: Len = 0
6656     // ArraySize > 0:   Len = NofStrings *(len(firstString) + len(separator))
6657     //           (Assuming that all Strings are roughly equally long)
6658     int bufSize = ((arraySize == 0) ? 0 : arraySize
6659         * ((array[0] == null ? 16 : array[0].toString().length()) + separator.length()));
6660 
6661     StringBuffer buf = new StringBuffer(bufSize);
6662 
6663     for (int i = 0; i < arraySize; i++) {
6664       if (i > 0) {
6665         buf.append(separator);
6666       }
6667       if (array[i] != null) {
6668         buf.append(array[i]);
6669       }
6670     }
6671     return buf.toString();
6672   }
6673 
6674   /**
6675    * <p>Joins the elements of the provided <code>Iterator</code> into
6676    * a single String containing the provided elements.</p>
6677    *
6678    * <p>No delimiter is added before or after the list. Null objects or empty
6679    * strings within the iteration are represented by empty strings.</p>
6680    *
6681    * <p>See the examples here: {@link #join(Object[],char)}. </p>
6682    *
6683    * @param iterator  the <code>Iterator</code> of values to join together, may be null
6684    * @param separator  the separator character to use
6685    * @return the joined String, <code>null</code> if null iterator input
6686    * @since 2.0
6687    */
6688   public static String join(Iterator iterator, char separator) {
6689     if (iterator == null) {
6690       return null;
6691     }
6692     StringBuffer buf = new StringBuffer(256); // Java default is 16, probably too small
6693     while (iterator.hasNext()) {
6694       Object obj = iterator.next();
6695       if (obj != null) {
6696         buf.append(obj);
6697       }
6698       if (iterator.hasNext()) {
6699         buf.append(separator);
6700       }
6701     }
6702     return buf.toString();
6703   }
6704 
6705   /**
6706    * <p>Joins the elements of the provided <code>Iterator</code> into
6707    * a single String containing the provided elements.</p>
6708    *
6709    * <p>No delimiter is added before or after the list.
6710    * A <code>null</code> separator is the same as an empty String ("").</p>
6711    *
6712    * <p>See the examples here: {@link #join(Object[],String)}. </p>
6713    *
6714    * @param iterator  the <code>Iterator</code> of values to join together, may be null
6715    * @param separator  the separator character to use, null treated as ""
6716    * @return the joined String, <code>null</code> if null iterator input
6717    */
6718   public static String join(Iterator iterator, String separator) {
6719     if (iterator == null) {
6720       return null;
6721     }
6722     StringBuffer buf = new StringBuffer(256); // Java default is 16, probably too small
6723     while (iterator.hasNext()) {
6724       Object obj = iterator.next();
6725       if (obj != null) {
6726         buf.append(obj);
6727       }
6728       if ((separator != null) && iterator.hasNext()) {
6729         buf.append(separator);
6730       }
6731     }
6732     return buf.toString();
6733   }
6734 
6735   /**
6736    * <p>Returns either the passed in String,
6737    * or if the String is <code>null</code>, an empty String ("").</p>
6738    *
6739    * <pre>
6740    * StringUtils.defaultString(null)  = ""
6741    * StringUtils.defaultString("")    = ""
6742    * StringUtils.defaultString("bat") = "bat"
6743    * </pre>
6744    *
6745    * @see String#valueOf(Object)
6746    * @param str  the String to check, may be null
6747    * @return the passed in String, or the empty String if it
6748    *  was <code>null</code>
6749    */
6750   public static String defaultString(String str) {
6751     return str == null ? "" : str;
6752   }
6753 
6754   /**
6755    * <p>Returns either the passed in String, or if the String is
6756    * <code>null</code>, the value of <code>defaultStr</code>.</p>
6757    *
6758    * <pre>
6759    * StringUtils.defaultString(null, "NULL")  = "NULL"
6760    * StringUtils.defaultString("", "NULL")    = ""
6761    * StringUtils.defaultString("bat", "NULL") = "bat"
6762    * </pre>
6763    *
6764    * @see String#valueOf(Object)
6765    * @param str  the String to check, may be null
6766    * @param defaultStr  the default String to return
6767    *  if the input is <code>null</code>, may be null
6768    * @return the passed in String, or the default if it was <code>null</code>
6769    */
6770   public static String defaultString(String str, String defaultStr) {
6771     return str == null ? defaultStr : str;
6772   }
6773 
6774   /**
6775    * <p>Returns either the passed in String, or if the String is
6776    * empty or <code>null</code>, the value of <code>defaultStr</code>.</p>
6777    *
6778    * <pre>
6779    * StringUtils.defaultIfEmpty(null, "NULL")  = "NULL"
6780    * StringUtils.defaultIfEmpty("", "NULL")    = "NULL"
6781    * StringUtils.defaultIfEmpty("bat", "NULL") = "bat"
6782    * </pre>
6783    *
6784    * @param str  the String to check, may be null
6785    * @param defaultStr  the default String to return
6786    *  if the input is empty ("") or <code>null</code>, may be null
6787    * @return the passed in String, or the default
6788    */
6789   public static String defaultIfEmpty(String str, String defaultStr) {
6790     return isEmpty(str) ? defaultStr : str;
6791   }
6792 
6793   /**
6794    * <p>Capitalizes a String changing the first letter to title case as
6795    * per {@link Character#toTitleCase(char)}. No other letters are changed.</p>
6796    *
6797    * A <code>null</code> input String returns <code>null</code>.</p>
6798    *
6799    * <pre>
6800    * StringUtils.capitalize(null)  = null
6801    * StringUtils.capitalize("")    = ""
6802    * StringUtils.capitalize("cat") = "Cat"
6803    * StringUtils.capitalize("cAt") = "CAt"
6804    * </pre>
6805    *
6806    * @param str  the String to capitalize, may be null
6807    * @return the capitalized String, <code>null</code> if null String input
6808    * @since 2.0
6809    */
6810   public static String capitalize(String str) {
6811     int strLen;
6812     if (str == null || (strLen = str.length()) == 0) {
6813       return str;
6814     }
6815     return new StringBuffer(strLen).append(Character.toTitleCase(str.charAt(0))).append(
6816         str.substring(1)).toString();
6817   }
6818 
6819   /**
6820    * <p>Checks if String contains a search character, handling <code>null</code>.
6821    * This method uses {@link String#indexOf(int)}.</p>
6822    *
6823    * <p>A <code>null</code> or empty ("") String will return <code>false</code>.</p>
6824    *
6825    * <pre>
6826    * StringUtils.contains(null, *)    = false
6827    * StringUtils.contains("", *)      = false
6828    * StringUtils.contains("abc", 'a') = true
6829    * StringUtils.contains("abc", 'z') = false
6830    * </pre>
6831    *
6832    * @param str  the String to check, may be null
6833    * @param searchChar  the character to find
6834    * @return true if the String contains the search character,
6835    *  false if not or <code>null</code> string input
6836    * @since 2.0
6837    */
6838   public static boolean contains(String str, char searchChar) {
6839     if (isEmpty(str)) {
6840       return false;
6841     }
6842     return str.indexOf(searchChar) >= 0;
6843   }
6844 
6845   /**
6846    * <p>Checks if String contains a search String, handling <code>null</code>.
6847    * This method uses {@link String#indexOf(int)}.</p>
6848    *
6849    * <p>A <code>null</code> String will return <code>false</code>.</p>
6850    *
6851    * <pre>
6852    * StringUtils.contains(null, *)     = false
6853    * StringUtils.contains(*, null)     = false
6854    * StringUtils.contains("", "")      = true
6855    * StringUtils.contains("abc", "")   = true
6856    * StringUtils.contains("abc", "a")  = true
6857    * StringUtils.contains("abc", "z")  = false
6858    * </pre>
6859    *
6860    * @param str  the String to check, may be null
6861    * @param searchStr  the String to find, may be null
6862    * @return true if the String contains the search String,
6863    *  false if not or <code>null</code> string input
6864    * @since 2.0
6865    */
6866   public static boolean contains(String str, String searchStr) {
6867     if (str == null || searchStr == null) {
6868       return false;
6869     }
6870     return str.indexOf(searchStr) >= 0;
6871   }
6872   
6873   /**
6874    * An empty immutable <code>String</code> array.
6875    */
6876   public static final String[] EMPTY_STRING_ARRAY = new String[0];
6877 
6878   /**
6879    * <p>Compares two objects for equality, where either one or both
6880    * objects may be <code>null</code>.</p>
6881    *
6882    * <pre>
6883    * ObjectUtils.equals(null, null)                  = true
6884    * ObjectUtils.equals(null, "")                    = false
6885    * ObjectUtils.equals("", null)                    = false
6886    * ObjectUtils.equals("", "")                      = true
6887    * ObjectUtils.equals(Boolean.TRUE, null)          = false
6888    * ObjectUtils.equals(Boolean.TRUE, "true")        = false
6889    * ObjectUtils.equals(Boolean.TRUE, Boolean.TRUE)  = true
6890    * ObjectUtils.equals(Boolean.TRUE, Boolean.FALSE) = false
6891    * </pre>
6892    *
6893    * @param object1  the first object, may be <code>null</code>
6894    * @param object2  the second object, may be <code>null</code>
6895    * @return <code>true</code> if the values of both objects are the same
6896    */
6897   public static boolean equals(Object object1, Object object2) {
6898       if (object1 == object2) {
6899           return true;
6900       }
6901       if ((object1 == null) || (object2 == null)) {
6902           return false;
6903       }
6904       return object1.equals(object2);
6905   }
6906 
6907   /**
6908    * <p>A way to get the entire nested stack-trace of an throwable.</p>
6909    *
6910    * @param throwable  the <code>Throwable</code> to be examined
6911    * @return the nested stack trace, with the root cause first
6912    * @since 2.0
6913    */
6914   public static String getFullStackTrace(Throwable throwable) {
6915       StringWriter sw = new StringWriter();
6916       PrintWriter pw = new PrintWriter(sw, true);
6917       Throwable[] ts = getThrowables(throwable);
6918       for (int i = 0; i < ts.length; i++) {
6919           ts[i].printStackTrace(pw);
6920           if (isNestedThrowable(ts[i])) {
6921               break;
6922           }
6923       }
6924       return sw.getBuffer().toString();
6925   }
6926   /**
6927    * <p>Returns the list of <code>Throwable</code> objects in the
6928    * exception chain.</p>
6929    * 
6930    * <p>A throwable without cause will return an array containing
6931    * one element - the input throwable.
6932    * A throwable with one cause will return an array containing
6933    * two elements. - the input throwable and the cause throwable.
6934    * A <code>null</code> throwable will return an array size zero.</p>
6935    *
6936    * @param throwable  the throwable to inspect, may be null
6937    * @return the array of throwables, never null
6938    */
6939   @SuppressWarnings("unchecked")
6940   public static Throwable[] getThrowables(Throwable throwable) {
6941       List list = new ArrayList();
6942       while (throwable != null) {
6943           list.add(throwable);
6944           throwable = getCause(throwable);
6945       }
6946       return (Throwable[]) list.toArray(new Throwable[list.size()]);
6947   }
6948   
6949   /**
6950    * <p>The names of methods commonly used to access a wrapped exception.</p>
6951    */
6952   private static String[] CAUSE_METHOD_NAMES = {
6953       "getCause",
6954       "getNextException",
6955       "getTargetException",
6956       "getException",
6957       "getSourceException",
6958       "getRootCause",
6959       "getCausedByException",
6960       "getNested",
6961       "getLinkedException",
6962       "getNestedException",
6963       "getLinkedCause",
6964       "getThrowable",
6965   };
6966 
6967   /**
6968    * <p>Checks whether this <code>Throwable</code> class can store a cause.</p>
6969    * 
6970    * <p>This method does <b>not</b> check whether it actually does store a cause.<p>
6971    *
6972    * @param throwable  the <code>Throwable</code> to examine, may be null
6973    * @return boolean <code>true</code> if nested otherwise <code>false</code>
6974    * @since 2.0
6975    */
6976   public static boolean isNestedThrowable(Throwable throwable) {
6977       if (throwable == null) {
6978           return false;
6979       }
6980 
6981       if (throwable instanceof SQLException) {
6982           return true;
6983       } else if (throwable instanceof InvocationTargetException) {
6984           return true;
6985       } else if (isThrowableNested()) {
6986           return true;
6987       }
6988 
6989       Class cls = throwable.getClass();
6990       for (int i = 0, isize = CAUSE_METHOD_NAMES.length; i < isize; i++) {
6991           try {
6992               Method method = cls.getMethod(CAUSE_METHOD_NAMES[i], (Class[])null);
6993               if (method != null && Throwable.class.isAssignableFrom(method.getReturnType())) {
6994                   return true;
6995               }
6996           } catch (NoSuchMethodException ignored) {
6997           } catch (SecurityException ignored) {
6998           }
6999       }
7000 
7001       try {
7002           Field field = cls.getField("detail");
7003           if (field != null) {
7004               return true;
7005           }
7006       } catch (NoSuchFieldException ignored) {
7007       } catch (SecurityException ignored) {
7008       }
7009 
7010       return false;
7011   }
7012 
7013   /**
7014    * <p>The Method object for JDK1.4 getCause.</p>
7015    */
7016   private static final Method THROWABLE_CAUSE_METHOD;
7017   static {
7018       Method getCauseMethod;
7019       try {
7020           getCauseMethod = Throwable.class.getMethod("getCause", (Class[])null);
7021       } catch (Exception e) {
7022           getCauseMethod = null;
7023       }
7024       THROWABLE_CAUSE_METHOD = getCauseMethod;
7025   }
7026   
7027   /**
7028    * <p>Checks if the Throwable class has a <code>getCause</code> method.</p>
7029    * 
7030    * <p>This is true for JDK 1.4 and above.</p>
7031    * 
7032    * @return true if Throwable is nestable
7033    * @since 2.0
7034    */
7035   public static boolean isThrowableNested() {
7036       return THROWABLE_CAUSE_METHOD != null;
7037   }
7038 
7039   /**
7040    * <p>Introspects the <code>Throwable</code> to obtain the cause.</p>
7041    * 
7042    * <p>The method searches for methods with specific names that return a 
7043    * <code>Throwable</code> object. This will pick up most wrapping exceptions,
7044    * including those from JDK 1.4, and</p>
7045    *
7046    * <p>The default list searched for are:</p>
7047    * <ul>
7048    *  <li><code>getCause()</code></li>
7049    *  <li><code>getNextException()</code></li>
7050    *  <li><code>getTargetException()</code></li>
7051    *  <li><code>getException()</code></li>
7052    *  <li><code>getSourceException()</code></li>
7053    *  <li><code>getRootCause()</code></li>
7054    *  <li><code>getCausedByException()</code></li>
7055    *  <li><code>getNested()</code></li>
7056    * </ul>
7057    * 
7058    * <p>In the absence of any such method, the object is inspected for a
7059    * <code>detail</code> field assignable to a <code>Throwable</code>.</p>
7060    * 
7061    * <p>If none of the above is found, returns <code>null</code>.</p>
7062    *
7063    * @param throwable  the throwable to introspect for a cause, may be null
7064    * @return the cause of the <code>Throwable</code>,
7065    *  <code>null</code> if none found or null throwable input
7066    * @since 1.0
7067    */
7068   public static Throwable getCause(Throwable throwable) {
7069       return getCause(throwable, CAUSE_METHOD_NAMES);
7070   }
7071 
7072   /**
7073    * <p>Introspects the <code>Throwable</code> to obtain the cause.</p>
7074    * 
7075    * <ol>
7076    * <li>Try known exception types.</li>
7077    * <li>Try the supplied array of method names.</li>
7078    * <li>Try the field 'detail'.</li>
7079    * </ol>
7080    * 
7081    * <p>A <code>null</code> set of method names means use the default set.
7082    * A <code>null</code> in the set of method names will be ignored.</p>
7083    *
7084    * @param throwable  the throwable to introspect for a cause, may be null
7085    * @param methodNames  the method names, null treated as default set
7086    * @return the cause of the <code>Throwable</code>,
7087    *  <code>null</code> if none found or null throwable input
7088    * @since 1.0
7089    */
7090   public static Throwable getCause(Throwable throwable, String[] methodNames) {
7091       if (throwable == null) {
7092           return null;
7093       }
7094       Throwable cause = getCauseUsingWellKnownTypes(throwable);
7095       if (cause == null) {
7096           if (methodNames == null) {
7097               methodNames = CAUSE_METHOD_NAMES;
7098           }
7099           for (int i = 0; i < methodNames.length; i++) {
7100               String methodName = methodNames[i];
7101               if (methodName != null) {
7102                   cause = getCauseUsingMethodName(throwable, methodName);
7103                   if (cause != null) {
7104                       break;
7105                   }
7106               }
7107           }
7108 
7109           if (cause == null) {
7110               cause = getCauseUsingFieldName(throwable, "detail");
7111           }
7112       }
7113       return cause;
7114   }
7115 
7116   /**
7117    * <p>Finds a <code>Throwable</code> by method name.</p>
7118    * 
7119    * @param throwable  the exception to examine
7120    * @param methodName  the name of the method to find and invoke
7121    * @return the wrapped exception, or <code>null</code> if not found
7122    */
7123   private static Throwable getCauseUsingMethodName(Throwable throwable, String methodName) {
7124       Method method = null;
7125       try {
7126           method = throwable.getClass().getMethod(methodName, (Class[])null);
7127       } catch (NoSuchMethodException ignored) {
7128       } catch (SecurityException ignored) {
7129       }
7130 
7131       if (method != null && Throwable.class.isAssignableFrom(method.getReturnType())) {
7132           try {
7133               return (Throwable) method.invoke(throwable, EMPTY_OBJECT_ARRAY);
7134           } catch (IllegalAccessException ignored) {
7135           } catch (IllegalArgumentException ignored) {
7136           } catch (InvocationTargetException ignored) {
7137           }
7138       }
7139       return null;
7140   }
7141 
7142   /**
7143    * <p>Finds a <code>Throwable</code> by field name.</p>
7144    * 
7145    * @param throwable  the exception to examine
7146    * @param fieldName  the name of the attribute to examine
7147    * @return the wrapped exception, or <code>null</code> if not found
7148    */
7149   private static Throwable getCauseUsingFieldName(Throwable throwable, String fieldName) {
7150       Field field = null;
7151       try {
7152           field = throwable.getClass().getField(fieldName);
7153       } catch (NoSuchFieldException ignored) {
7154       } catch (SecurityException ignored) {
7155       }
7156 
7157       if (field != null && Throwable.class.isAssignableFrom(field.getType())) {
7158           try {
7159               return (Throwable) field.get(throwable);
7160           } catch (IllegalAccessException ignored) {
7161           } catch (IllegalArgumentException ignored) {
7162           }
7163       }
7164       return null;
7165   }
7166 
7167   /**
7168    * <p>Finds a <code>Throwable</code> for known types.</p>
7169    * 
7170    * <p>Uses <code>instanceof</code> checks to examine the exception,
7171    * looking for well known types which could contain chained or
7172    * wrapped exceptions.</p>
7173    *
7174    * @param throwable  the exception to examine
7175    * @return the wrapped exception, or <code>null</code> if not found
7176    */
7177   private static Throwable getCauseUsingWellKnownTypes(Throwable throwable) {
7178       if (throwable instanceof SQLException) {
7179           return ((SQLException) throwable).getNextException();
7180       } else if (throwable instanceof InvocationTargetException) {
7181           return ((InvocationTargetException) throwable).getTargetException();
7182       } else {
7183           return null;
7184       }
7185   }
7186 
7187   /**
7188    * An empty immutable <code>Object</code> array.
7189    */
7190   public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
7191   
7192   /** 
7193    * from a command line arg, get the key.  e.g. if input is --whatever=something
7194    * then key is whatever, value is something 
7195    * @param option 
7196    * @return the key */
7197   public static String argKey(String option) {
7198     int equalsIndex = option.indexOf("=");
7199     if (equalsIndex == -1) {
7200       throw new RuntimeException("Invalid option: " + option + ", it should look like: --someOption=someValue");
7201     }
7202     String key = option.substring(0,equalsIndex);
7203     if (!key.startsWith("--")) {
7204       throw new RuntimeException("Invalid option: " + option + ", it should look like: --someOption=someValue");
7205     }
7206     key = key.substring(2);
7207     return key;
7208   }
7209 
7210   /** 
7211    * from a command line arg, get the key.  e.g. if input is --whatever=something
7212    * then key is whatever, value is something 
7213    * @param option 
7214    * @return the value
7215    */
7216   public static String argValue(String option) {
7217     int equalsIndex = option.indexOf("=");
7218     if (equalsIndex == -1) {
7219       throw new RuntimeException("Invalid option: " + option + ", it should look like: --someOption=someValue");
7220     }
7221     String value = option.substring(equalsIndex+1, option.length());
7222     return value;
7223   }
7224   
7225   /** add an option: --whatever=val   to a map of options where --whatever is key, and val is value 
7226    * @param args 
7227    * @return the map
7228    */
7229   public static Map<String, String> argMap(String[] args) {
7230     
7231     Map<String, String> result = new LinkedHashMap<String, String>();
7232 
7233     for (String arg : nonNull(args,String.class)) {
7234       String key = argKey(arg);
7235       String value = argValue(arg);
7236       if (result.containsKey(key)) {
7237         throw new RuntimeException("Passing key twice: " + key);
7238       }
7239       result.put(key, value);
7240     }
7241     
7242     return result;
7243   }
7244   
7245   /**
7246    * get the value from the argMap, throw exception if not there and required
7247    * @param argMap
7248    * @param argMapNotUsed 
7249    * @param key
7250    * @param required
7251    * @return the value or null or exception
7252    */
7253   public static String argMapString(Map<String, String> argMap, Map<String, String> argMapNotUsed, 
7254       String key, boolean required) {
7255 
7256     if (argMap.containsKey(key)) {
7257       
7258       //keep track that this is gone
7259       argMapNotUsed.remove(key);
7260       
7261       return argMap.get(key);
7262     }
7263     if (required) {
7264       throw new RuntimeException("Argument '--" + key + "' is required, but not specified.  e.g. --" + key + "=value");
7265     }
7266     return null;
7267 
7268   }
7269   
7270   /**
7271    * get the value from the argMap, throw exception if not there and required
7272    * @param argMap
7273    * @param argMapNotUsed 
7274    * @param key
7275    * @param required
7276    * @param defaultValue 
7277    * @return the value or null or exception
7278    */
7279   public static boolean argMapBoolean(Map<String, String> argMap, Map<String, String> argMapNotUsed, 
7280       String key, boolean required, boolean defaultValue) {
7281     String argString = argMapString(argMap, argMapNotUsed, key, required);
7282 
7283     if (isBlank(argString) && required) {
7284       throw new RuntimeException("Argument '--" + key + "' is required, but not specified.  e.g. --" + key + "=true");
7285     }
7286     return booleanValue(argString, defaultValue);
7287   }
7288   
7289   /**
7290    * get the value from the argMap, throw exception if not there and required
7291    * @param argMap
7292    * @param argMapNotUsed 
7293    * @param key
7294    * @return the value or null or exception
7295    */
7296   public static Timestamp argMapTimestamp(Map<String, String> argMap, Map<String, String> argMapNotUsed, 
7297       String key) {
7298     String argString = argMapString(argMap, argMapNotUsed, key, false);
7299     if (isBlank(argString)) {
7300       return null;
7301     }
7302     Date date = stringToDate2(argString);
7303     return new Timestamp(date.getTime());
7304   }
7305   
7306   /**
7307    * get the value from the argMap
7308    * @param argMap
7309    * @param argMapNotUsed 
7310    * @param key
7311    * @return the value or null or exception
7312    */
7313   public static Boolean argMapBoolean(Map<String, String> argMap, Map<String, String> argMapNotUsed, 
7314       String key) {
7315     String argString = argMapString(argMap, argMapNotUsed, key, false);
7316 
7317     return booleanObjectValue(argString);
7318   }
7319   
7320   /**
7321    * get the set from comma separated from the argMap, throw exception if not there and required
7322    * @param argMap
7323    * @param argMapNotUsed 
7324    * @param key
7325    * @param required
7326    * @return the value or null or exception
7327    */
7328   public static Set<String> argMapSet(Map<String, String> argMap, Map<String, String> argMapNotUsed, 
7329       String key, boolean required) {
7330     List<String> list = argMapList(argMap, argMapNotUsed, key, required);
7331     return list == null ? null : new LinkedHashSet(list);
7332   }
7333   
7334   /**
7335    * get the list from comma separated from the argMap, throw exception if not there and required
7336    * @param argMap
7337    * @param argMapNotUsed 
7338    * @param key
7339    * @param required
7340    * @return the value or null or exception
7341    */
7342   public static List<String> argMapList(Map<String, String> argMap, Map<String, String> argMapNotUsed, 
7343       String key, boolean required) {
7344     String argString = argMapString(argMap, argMapNotUsed, key, required);
7345     if (isBlank(argString)) {
7346       return null;
7347     }
7348     return splitTrimToList(argString, ",");
7349   }
7350   
7351   /**
7352    * get the list from comma separated from the argMap, throw exception if not there and required
7353    * @param argMap
7354    * @param argMapNotUsed 
7355    * @param key
7356    * @param required
7357    * @return the value or null or exception
7358    */
7359   public static List<String> argMapFileList(Map<String, String> argMap, Map<String, String> argMapNotUsed, 
7360       String key, boolean required) {
7361     String argString = argMapString(argMap, argMapNotUsed, key, required);
7362     if (isBlank(argString)) {
7363       return null;
7364     }
7365     //read from file
7366     File file = new File(argString);
7367     try {
7368       //do this by regex, since we dont know what platform we are on
7369       String listString = readFileIntoString(file);
7370       String[] array = listString.split("\\s+");
7371       List<String> list = new ArrayList<String>();
7372       for (String string : array) {
7373         //dont know if any here will be blank or whatnot
7374         if (!isBlank(string)) {
7375           //should already be trimmed, but just in case
7376           list.add(trim(string));
7377         }
7378       }
7379       return list;
7380     } catch (Exception e) {
7381       throw new RuntimeException("Error reading file: '" 
7382           + fileCanonicalPath(file) + "' from command line arg: " + key, e );
7383     }
7384   }
7385   
7386   /**
7387    * Copy bytes from an <code>InputStream</code> to chars on a
7388    * <code>Writer</code> using the default character encoding of the platform.
7389    * <p>
7390    * This method buffers the input internally, so there is no need to use a
7391    * <code>BufferedInputStream</code>.
7392    * <p>
7393    * This method uses {@link InputStreamReader}.
7394    *
7395    * @param input  the <code>InputStream</code> to read from
7396    * @param output  the <code>Writer</code> to write to
7397    * @throws NullPointerException if the input or output is null
7398    * @throws IOException if an I/O error occurs
7399    * @since Commons IO 1.1
7400    */
7401   public static void copy(InputStream input, Writer output)
7402           throws IOException {
7403       String charsetName = "UTF-8";
7404       InputStreamReader in = new InputStreamReader(input, charsetName);
7405       copy(in, output);
7406   }
7407   
7408   /**
7409    * Copy bytes from an <code>InputStream</code> to an
7410    * <code>OutputStream</code>.
7411    * <p>
7412    * This method buffers the input internally, so there is no need to use a
7413    * <code>BufferedInputStream</code>.
7414    * <p>
7415    * Large streams (over 2GB) will return a bytes copied value of
7416    * <code>-1</code> after the copy has completed since the correct
7417    * number of bytes cannot be returned as an int. For large streams
7418    * use the <code>copyLarge(InputStream, OutputStream)</code> method.
7419    * 
7420    * @param input  the <code>InputStream</code> to read from
7421    * @param output  the <code>OutputStream</code> to write to
7422    * @return the number of bytes copied
7423    * @throws NullPointerException if the input or output is null
7424    * @throws IOException if an I/O error occurs
7425    * @throws ArithmeticException if the byte count is too large
7426    * @since Commons IO 1.1
7427    */
7428   public static int copy(InputStream input, OutputStream output) throws IOException {
7429       long count = copyLarge(input, output);
7430       if (count > Integer.MAX_VALUE) {
7431           return -1;
7432       }
7433       return (int) count;
7434   }
7435 
7436   /**
7437    * Copy bytes from a large (over 2GB) <code>InputStream</code> to an
7438    * <code>OutputStream</code>.
7439    * <p>
7440    * This method buffers the input internally, so there is no need to use a
7441    * <code>BufferedInputStream</code>.
7442    * 
7443    * @param input  the <code>InputStream</code> to read from
7444    * @param output  the <code>OutputStream</code> to write to
7445    * @return the number of bytes copied
7446    * @throws NullPointerException if the input or output is null
7447    * @throws IOException if an I/O error occurs
7448    * @since Commons IO 1.3
7449    */
7450   public static long copyLarge(InputStream input, OutputStream output)
7451           throws IOException {
7452       byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
7453       long count = 0;
7454       int n = 0;
7455       while (-1 != (n = input.read(buffer))) {
7456           output.write(buffer, 0, n);
7457           count += n;
7458       }
7459       return count;
7460   }
7461 
7462   /**
7463    * get a jar file from a sample class
7464    * @param sampleClass
7465    * @return the jar file
7466    */
7467   public static File jarFile(Class sampleClass) {
7468     try {
7469       CodeSource codeSource = sampleClass.getProtectionDomain().getCodeSource();
7470       if (codeSource != null && codeSource.getLocation() != null) {
7471         String fileName = URLDecoder.decode(codeSource.getLocation().getFile(), "UTF-8");
7472         return new File(fileName);
7473       }
7474       String resourcePath = sampleClass.getName();
7475       resourcePath = resourcePath.replace('.', '/') + ".class";
7476       URL url = computeUrl(resourcePath, true);
7477       String urlPath = url.toString();
7478       
7479       if (urlPath.startsWith("jar:")) {
7480         urlPath = urlPath.substring(4);
7481       }
7482       if (urlPath.startsWith("file:")) {
7483         urlPath = urlPath.substring(5);
7484       }
7485       urlPath = prefixOrSuffix(urlPath, "!", true); 
7486   
7487       urlPath = URLDecoder.decode(urlPath, "UTF-8");
7488   
7489       File file = new File(urlPath);
7490       if (urlPath.endsWith(".jar") && file.exists() && file.isFile()) {
7491         return file;
7492       }
7493     } catch (Exception e) {
7494     }
7495     return null;
7496   }
7497 
7498   /**
7499    * strip the last slash (/ or \) from a string if it exists
7500    * 
7501    * @param input
7502    * 
7503    * @return input - the last / or \
7504    */
7505   public static String stripLastSlashIfExists(String input) {
7506     if ((input == null) || (input.length() == 0)) {
7507       return null;
7508     }
7509 
7510     char lastChar = input.charAt(input.length() - 1);
7511 
7512     if ((lastChar == '\\') || (lastChar == '/')) {
7513       return input.substring(0, input.length() - 1);
7514     }
7515 
7516     return input;
7517   }
7518 
7519   /**
7520    * retrieve a password from stdin
7521    * @param dontMask
7522    * @param prompt to print to user
7523    * @return the password
7524    */
7525   public static String retrievePasswordFromStdin(boolean dontMask, String prompt) {
7526     String passwordString = null;
7527 
7528     if (dontMask) {
7529 
7530       System.out.print(prompt);
7531       //  open up standard input 
7532       BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 
7533 
7534       //  read the username from the command-line; need to use try/catch with the 
7535       //  readLine() method 
7536       try { 
7537          passwordString = br.readLine(); 
7538       } catch (IOException ioe) { 
7539          System.out.println("IO error! " + getFullStackTrace(ioe));
7540          System.exit(1); 
7541       } 
7542 
7543     } else {
7544       char password[] = null;
7545       try {
7546         password = retrievePasswordFromStdin(System.in, prompt);
7547       } catch (IOException ioe) {
7548         ioe.printStackTrace();
7549       }
7550       passwordString = String.valueOf(password);
7551     } 
7552     return passwordString;
7553     
7554   }
7555 
7556   /**
7557    * @param in stream to be used (e.g. System.in)
7558    * @param prompt The prompt to display to the user.
7559    * @return The password as entered by the user.
7560    * @throws IOException 
7561    */
7562   public static final char[] retrievePasswordFromStdin(InputStream in, String prompt) throws IOException {
7563     MaskingThread maskingthread = new MaskingThread(prompt);
7564 
7565     Thread thread = new Thread(maskingthread);
7566     thread.start();
7567 
7568     char[] lineBuffer;
7569     char[] buf;
7570 
7571     buf = lineBuffer = new char[128];
7572 
7573     int room = buf.length;
7574     int offset = 0;
7575     int c;
7576 
7577     loop: while (true) {
7578       switch (c = in.read()) {
7579         case -1:
7580         case '\n':
7581           break loop;
7582 
7583         case '\r':
7584           int c2 = in.read();
7585           if ((c2 != '\n') && (c2 != -1)) {
7586             if (!(in instanceof PushbackInputStream)) {
7587               in = new PushbackInputStream(in);
7588             }
7589             ((PushbackInputStream) in).unread(c2);
7590           } else {
7591             break loop;
7592           }
7593 
7594         default:
7595           if (--room < 0) {
7596             buf = new char[offset + 128];
7597             room = buf.length - offset - 1;
7598             System.arraycopy(lineBuffer, 0, buf, 0, offset);
7599             Arrays.fill(lineBuffer, ' ');
7600             lineBuffer = buf;
7601           }
7602           buf[offset++] = (char) c;
7603           break;
7604       }
7605     }
7606     maskingthread.stopMasking();
7607     if (offset == 0) {
7608       return null;
7609     }
7610     char[] ret = new char[offset];
7611     System.arraycopy(buf, 0, ret, 0, offset);
7612     Arrays.fill(buf, ' ');
7613     return ret;
7614   }
7615 
7616   /**
7617    * thread to mask input
7618    */
7619   static class MaskingThread extends Thread {
7620 
7621     /** stop */
7622     private volatile boolean stop;
7623 
7624     /** echo char, this doesnt work correctly, so make a space so people dont notice...  
7625      * prints out too many */
7626     private char echochar = ' ';
7627 
7628     /**
7629      *@param prompt The prompt displayed to the user
7630      */
7631     public MaskingThread(String prompt) {
7632       System.out.print(prompt);
7633     }
7634 
7635     /**
7636      * Begin masking until asked to stop.
7637      */
7638     @Override
7639     public void run() {
7640 
7641       int priority = Thread.currentThread().getPriority();
7642       Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
7643 
7644       try {
7645         this.stop = true;
7646         while (this.stop) {
7647           System.out.print("\010" + this.echochar);
7648           try {
7649             // attempt masking at this rate
7650             Thread.sleep(1);
7651           } catch (InterruptedException iex) {
7652             Thread.currentThread().interrupt();
7653             return;
7654           }
7655         }
7656       } finally { // restore the original priority
7657         Thread.currentThread().setPriority(priority);
7658       }
7659     }
7660 
7661     /**
7662      * Instruct the thread to stop masking.
7663      */
7664     public void stopMasking() {
7665       this.stop = false;
7666     }
7667   }
7668 
7669   /**
7670    * make sure a value exists in properties
7671    * @param resourceName
7672    * @param properties 
7673    * @param overrideMap 
7674    * @param key
7675    * @param exceptionOnError 
7676    * @return true if ok, false if not
7677    */
7678   public static boolean propertyValidateValueRequired(String resourceName, Properties properties, 
7679       Map<String, String> overrideMap, String key, boolean exceptionOnError) {
7680     
7681     Map<String, String> threadLocalMap = propertiesThreadLocalOverrideMap(resourceName);
7682 
7683     String value = propertiesValue(properties, threadLocalMap, overrideMap, key);
7684 
7685     if (!isBlank(value)) {
7686       return true;
7687     }
7688     String error = "Cant find property " + key + " in resource: " + resourceName + ", it is required";
7689     
7690     if (exceptionOnError) {
7691       throw new RuntimeException(error);
7692     }
7693     
7694     System.err.println("Grouper error: " + error);
7695     return false;
7696   }
7697 
7698   /**
7699    * make sure a value is boolean in properties
7700    * @param resourceName
7701    * @param properties 
7702    * @param overrideMap
7703    * @param key
7704    * @param required
7705    * @param exceptionOnError 
7706    * @return true if ok, false if not
7707    */
7708   public static boolean propertyValidateValueBoolean(String resourceName, Properties properties, 
7709       Map<String, String> overrideMap, String key, 
7710       boolean required, boolean exceptionOnError) {
7711     
7712     if (required && !propertyValidateValueRequired(resourceName, properties, 
7713         overrideMap, key, exceptionOnError)) {
7714       return false;
7715     }
7716   
7717     Map<String, String> threadLocalMap = propertiesThreadLocalOverrideMap(resourceName);
7718 
7719     String value = propertiesValue(properties, threadLocalMap, overrideMap, key);
7720     //maybe ok not there
7721     if (!required && isBlank(value)) {
7722       return true;
7723     }
7724     try {
7725       booleanValue(value);
7726       return true;
7727     } catch (Exception e) {
7728       
7729     }
7730     String error = "Expecting true or false property " + key + " in resource: " + resourceName + ", but is '" + value + "'";
7731     if (exceptionOnError) {
7732       throw new RuntimeException(error);
7733     }
7734     System.err.println("Grouper error: " + error);
7735     return false;
7736   }
7737 
7738   
7739   /**
7740    * make sure a value is int in properties
7741    * @param resourceName
7742    * @param properties 
7743    * @param overrideMap
7744    * @param key
7745    * @param required
7746    * @param exceptionOnError 
7747    * @return true if ok, false if not
7748    */
7749   public static boolean propertyValidateValueInt(String resourceName, Properties properties, 
7750       Map<String, String> overrideMap, String key, 
7751       boolean required, boolean exceptionOnError) {
7752     
7753     if (required && !propertyValidateValueRequired(resourceName, properties, 
7754         overrideMap, key, exceptionOnError)) {
7755       return false;
7756     }
7757   
7758     Map<String, String> threadLocalMap = propertiesThreadLocalOverrideMap(resourceName);
7759     
7760     String value = propertiesValue(properties, threadLocalMap, overrideMap, key);
7761     //maybe ok not there
7762     if (!required && isBlank(value)) {
7763       return true;
7764     }
7765     try {
7766       intValue(value);
7767       return true;
7768     } catch (Exception e) {
7769       
7770     }
7771     String error = "Expecting integer property " + key + " in resource: " + resourceName + ", but is '" + value + "'";
7772     if (exceptionOnError) {
7773       throw new RuntimeException(error);
7774     }
7775     System.err.println("Grouper error: " + error);
7776     return false;
7777   }
7778 
7779   /**
7780    * make sure a property is a class of a certain type
7781    * @param resourceName
7782    * @param properties 
7783    * @param overrideMap
7784    * @param key
7785    * @param classType
7786    * @param required 
7787    * @param exceptionOnError
7788    * @return true if ok
7789    */
7790   public static boolean propertyValidateValueClass(String resourceName, Properties properties, 
7791       Map<String, String> overrideMap, String key, Class<?> classType, boolean required, boolean exceptionOnError) {
7792   
7793     if (required && !propertyValidateValueRequired(resourceName, properties, 
7794         overrideMap, key, exceptionOnError)) {
7795       return false;
7796     }
7797     String value = propertiesValue(properties, overrideMap, key);
7798   
7799     //maybe ok not there
7800     if (!required && isBlank(value)) {
7801       return true;
7802     }
7803     
7804     String extraError = "";
7805     try {
7806       
7807       
7808       Class<?> theClass = forName(value);
7809       if (classType.isAssignableFrom(theClass)) {
7810         return true;
7811       }
7812       extraError = " does not derive from class: " + classType.getSimpleName();
7813       
7814     } catch (Exception e) {
7815       extraError = ", " + getFullStackTrace(e);
7816     }
7817     String error = "Cant process property " + key + " in resource: " + resourceName + ", the current" +
7818         " value is '" + value + "', which should be of type: " 
7819         + classType.getName() + extraError;
7820     if (exceptionOnError) {
7821       throw new RuntimeException(error);
7822     }
7823     System.err.println("Grouper error: " + error);
7824     return false;
7825 
7826   }
7827 
7828   /**
7829    * <p>Strips any of a set of characters from the start of a String.</p>
7830    *
7831    * <p>A <code>null</code> input String returns <code>null</code>.
7832    * An empty string ("") input returns the empty string.</p>
7833    *
7834    * <p>If the stripChars String is <code>null</code>, whitespace is
7835    * stripped as defined by {@link Character#isWhitespace(char)}.</p>
7836    *
7837    * <pre>
7838    * StringUtils.stripStart(null, *)          = null
7839    * StringUtils.stripStart("", *)            = ""
7840    * StringUtils.stripStart("abc", "")        = "abc"
7841    * StringUtils.stripStart("abc", null)      = "abc"
7842    * StringUtils.stripStart("  abc", null)    = "abc"
7843    * StringUtils.stripStart("abc  ", null)    = "abc  "
7844    * StringUtils.stripStart(" abc ", null)    = "abc "
7845    * StringUtils.stripStart("yxabc  ", "xyz") = "abc  "
7846    * </pre>
7847    *
7848    * @param str  the String to remove characters from, may be null
7849    * @param stripChars  the characters to remove, null treated as whitespace
7850    * @return the stripped String, <code>null</code> if null String input
7851    */
7852   public static String stripStart(String str, String stripChars) {
7853     int strLen;
7854     if (str == null || (strLen = str.length()) == 0) {
7855       return str;
7856     }
7857     int start = 0;
7858     if (stripChars == null) {
7859       while ((start != strLen) && Character.isWhitespace(str.charAt(start))) {
7860         start++;
7861       }
7862     } else if (stripChars.length() == 0) {
7863       return str;
7864     } else {
7865       while ((start != strLen) && (stripChars.indexOf(str.charAt(start)) != -1)) {
7866         start++;
7867       }
7868     }
7869     return str.substring(start);
7870   }
7871 
7872   /**
7873    * <p>Strips any of a set of characters from the end of a String.</p>
7874    *
7875    * <p>A <code>null</code> input String returns <code>null</code>.
7876    * An empty string ("") input returns the empty string.</p>
7877    *
7878    * <p>If the stripChars String is <code>null</code>, whitespace is
7879    * stripped as defined by {@link Character#isWhitespace(char)}.</p>
7880    *
7881    * <pre>
7882    * StringUtils.stripEnd(null, *)          = null
7883    * StringUtils.stripEnd("", *)            = ""
7884    * StringUtils.stripEnd("abc", "")        = "abc"
7885    * StringUtils.stripEnd("abc", null)      = "abc"
7886    * StringUtils.stripEnd("  abc", null)    = "  abc"
7887    * StringUtils.stripEnd("abc  ", null)    = "abc"
7888    * StringUtils.stripEnd(" abc ", null)    = " abc"
7889    * StringUtils.stripEnd("  abcyx", "xyz") = "  abc"
7890    * </pre>
7891    *
7892    * @param str  the String to remove characters from, may be null
7893    * @param stripChars  the characters to remove, null treated as whitespace
7894    * @return the stripped String, <code>null</code> if null String input
7895    */
7896   public static String stripEnd(String str, String stripChars) {
7897     int end;
7898     if (str == null || (end = str.length()) == 0) {
7899       return str;
7900     }
7901 
7902     if (stripChars == null) {
7903       while ((end != 0) && Character.isWhitespace(str.charAt(end - 1))) {
7904         end--;
7905       }
7906     } else if (stripChars.length() == 0) {
7907       return str;
7908     } else {
7909       while ((end != 0) && (stripChars.indexOf(str.charAt(end - 1)) != -1)) {
7910         end--;
7911       }
7912     }
7913     return str.substring(0, end);
7914   }
7915 
7916   /**
7917    * The empty String <code>""</code>.
7918    * @since 2.0
7919    */
7920   public static final String EMPTY = "";
7921 
7922   /**
7923    * Represents a failed index search.
7924    * @since 2.1
7925    */
7926   public static final int INDEX_NOT_FOUND = -1;
7927 
7928   /**
7929    * <p>The maximum size to which the padding constant(s) can expand.</p>
7930    */
7931   private static final int PAD_LIMIT = 8192;
7932 
7933   /**
7934    * <p>An array of <code>String</code>s used for padding.</p>
7935    *
7936    * <p>Used for efficient space padding. The length of each String expands as needed.</p>
7937    */
7938   private static final String[] PADDING = new String[Character.MAX_VALUE];
7939 
7940   static {
7941     // space padding is most common, start with 64 chars
7942     PADDING[32] = "                                                                ";
7943   }
7944 
7945   /**
7946    * <p>Repeat a String <code>repeat</code> times to form a
7947    * new String.</p>
7948    *
7949    * <pre>
7950    * StringUtils.repeat(null, 2) = null
7951    * StringUtils.repeat("", 0)   = ""
7952    * StringUtils.repeat("", 2)   = ""
7953    * StringUtils.repeat("a", 3)  = "aaa"
7954    * StringUtils.repeat("ab", 2) = "abab"
7955    * StringUtils.repeat("a", -2) = ""
7956    * </pre>
7957    *
7958    * @param str  the String to repeat, may be null
7959    * @param repeat  number of times to repeat str, negative treated as zero
7960    * @return a new String consisting of the original String repeated,
7961    *  <code>null</code> if null String input
7962    */
7963   public static String repeat(String str, int repeat) {
7964     // Performance tuned for 2.0 (JDK1.4)
7965 
7966     if (str == null) {
7967       return null;
7968     }
7969     if (repeat <= 0) {
7970       return EMPTY;
7971     }
7972     int inputLength = str.length();
7973     if (repeat == 1 || inputLength == 0) {
7974       return str;
7975     }
7976     if (inputLength == 1 && repeat <= PAD_LIMIT) {
7977       return padding(repeat, str.charAt(0));
7978     }
7979 
7980     int outputLength = inputLength * repeat;
7981     switch (inputLength) {
7982       case 1:
7983         char ch = str.charAt(0);
7984         char[] output1 = new char[outputLength];
7985         for (int i = repeat - 1; i >= 0; i--) {
7986           output1[i] = ch;
7987         }
7988         return new String(output1);
7989       case 2:
7990         char ch0 = str.charAt(0);
7991         char ch1 = str.charAt(1);
7992         char[] output2 = new char[outputLength];
7993         for (int i = repeat * 2 - 2; i >= 0; i--, i--) {
7994           output2[i] = ch0;
7995           output2[i + 1] = ch1;
7996         }
7997         return new String(output2);
7998       default:
7999         StringBuffer buf = new StringBuffer(outputLength);
8000         for (int i = 0; i < repeat; i++) {
8001           buf.append(str);
8002         }
8003         return buf.toString();
8004     }
8005   }
8006 
8007   /**
8008    * <p>Returns padding using the specified delimiter repeated
8009    * to a given length.</p>
8010    *
8011    * <pre>
8012    * StringUtils.padding(0, 'e')  = ""
8013    * StringUtils.padding(3, 'e')  = "eee"
8014    * StringUtils.padding(-2, 'e') = IndexOutOfBoundsException
8015    * </pre>
8016    *
8017    * @param repeat  number of times to repeat delim
8018    * @param padChar  character to repeat
8019    * @return String with repeated character
8020    * @throws IndexOutOfBoundsException if <code>repeat &lt; 0</code>
8021    */
8022   private static String padding(int repeat, char padChar) {
8023     // be careful of synchronization in this method
8024     // we are assuming that get and set from an array index is atomic
8025     String pad = PADDING[padChar];
8026     if (pad == null) {
8027       pad = String.valueOf(padChar);
8028     }
8029     while (pad.length() < repeat) {
8030       pad = pad.concat(pad);
8031     }
8032     PADDING[padChar] = pad;
8033     return pad.substring(0, repeat);
8034   }
8035 
8036   /**
8037    * <p>Right pad a String with spaces (' ').</p>
8038    *
8039    * <p>The String is padded to the size of <code>size</code>.</p>
8040    *
8041    * <pre>
8042    * StringUtils.rightPad(null, *)   = null
8043    * StringUtils.rightPad("", 3)     = "   "
8044    * StringUtils.rightPad("bat", 3)  = "bat"
8045    * StringUtils.rightPad("bat", 5)  = "bat  "
8046    * StringUtils.rightPad("bat", 1)  = "bat"
8047    * StringUtils.rightPad("bat", -1) = "bat"
8048    * </pre>
8049    *
8050    * @param str  the String to pad out, may be null
8051    * @param size  the size to pad to
8052    * @return right padded String or original String if no padding is necessary,
8053    *  <code>null</code> if null String input
8054    */
8055   public static String rightPad(String str, int size) {
8056     return rightPad(str, size, ' ');
8057   }
8058 
8059   /**
8060    * <p>Right pad a String with a specified character.</p>
8061    *
8062    * <p>The String is padded to the size of <code>size</code>.</p>
8063    *
8064    * <pre>
8065    * StringUtils.rightPad(null, *, *)     = null
8066    * StringUtils.rightPad("", 3, 'z')     = "zzz"
8067    * StringUtils.rightPad("bat", 3, 'z')  = "bat"
8068    * StringUtils.rightPad("bat", 5, 'z')  = "batzz"
8069    * StringUtils.rightPad("bat", 1, 'z')  = "bat"
8070    * StringUtils.rightPad("bat", -1, 'z') = "bat"
8071    * </pre>
8072    *
8073    * @param str  the String to pad out, may be null
8074    * @param size  the size to pad to
8075    * @param padChar  the character to pad with
8076    * @return right padded String or original String if no padding is necessary,
8077    *  <code>null</code> if null String input
8078    * @since 2.0
8079    */
8080   public static String rightPad(String str, int size, char padChar) {
8081     if (str == null) {
8082       return null;
8083     }
8084     int pads = size - str.length();
8085     if (pads <= 0) {
8086       return str; // returns original String when possible
8087     }
8088     if (pads > PAD_LIMIT) {
8089       return rightPad(str, size, String.valueOf(padChar));
8090     }
8091     return str.concat(padding(pads, padChar));
8092   }
8093 
8094   /**
8095    * <p>Right pad a String with a specified String.</p>
8096    *
8097    * <p>The String is padded to the size of <code>size</code>.</p>
8098    *
8099    * <pre>
8100    * StringUtils.rightPad(null, *, *)      = null
8101    * StringUtils.rightPad("", 3, "z")      = "zzz"
8102    * StringUtils.rightPad("bat", 3, "yz")  = "bat"
8103    * StringUtils.rightPad("bat", 5, "yz")  = "batyz"
8104    * StringUtils.rightPad("bat", 8, "yz")  = "batyzyzy"
8105    * StringUtils.rightPad("bat", 1, "yz")  = "bat"
8106    * StringUtils.rightPad("bat", -1, "yz") = "bat"
8107    * StringUtils.rightPad("bat", 5, null)  = "bat  "
8108    * StringUtils.rightPad("bat", 5, "")    = "bat  "
8109    * </pre>
8110    *
8111    * @param str  the String to pad out, may be null
8112    * @param size  the size to pad to
8113    * @param padStr  the String to pad with, null or empty treated as single space
8114    * @return right padded String or original String if no padding is necessary,
8115    *  <code>null</code> if null String input
8116    */
8117   public static String rightPad(String str, int size, String padStr) {
8118     if (str == null) {
8119       return null;
8120     }
8121     if (isEmpty(padStr)) {
8122       padStr = " ";
8123     }
8124     int padLen = padStr.length();
8125     int strLen = str.length();
8126     int pads = size - strLen;
8127     if (pads <= 0) {
8128       return str; // returns original String when possible
8129     }
8130     if (padLen == 1 && pads <= PAD_LIMIT) {
8131       return rightPad(str, size, padStr.charAt(0));
8132     }
8133 
8134     if (pads == padLen) {
8135       return str.concat(padStr);
8136     } else if (pads < padLen) {
8137       return str.concat(padStr.substring(0, pads));
8138     } else {
8139       char[] padding = new char[pads];
8140       char[] padChars = padStr.toCharArray();
8141       for (int i = 0; i < pads; i++) {
8142         padding[i] = padChars[i % padLen];
8143       }
8144       return str.concat(new String(padding));
8145     }
8146   }
8147 
8148   /**
8149    * <p>Left pad a String with spaces (' ').</p>
8150    *
8151    * <p>The String is padded to the size of <code>size<code>.</p>
8152    *
8153    * <pre>
8154    * StringUtils.leftPad(null, *)   = null
8155    * StringUtils.leftPad("", 3)     = "   "
8156    * StringUtils.leftPad("bat", 3)  = "bat"
8157    * StringUtils.leftPad("bat", 5)  = "  bat"
8158    * StringUtils.leftPad("bat", 1)  = "bat"
8159    * StringUtils.leftPad("bat", -1) = "bat"
8160    * </pre>
8161    *
8162    * @param str  the String to pad out, may be null
8163    * @param size  the size to pad to
8164    * @return left padded String or original String if no padding is necessary,
8165    *  <code>null</code> if null String input
8166    */
8167   public static String leftPad(String str, int size) {
8168     return leftPad(str, size, ' ');
8169   }
8170 
8171   /**
8172    * <p>Left pad a String with a specified character.</p>
8173    *
8174    * <p>Pad to a size of <code>size</code>.</p>
8175    *
8176    * <pre>
8177    * StringUtils.leftPad(null, *, *)     = null
8178    * StringUtils.leftPad("", 3, 'z')     = "zzz"
8179    * StringUtils.leftPad("bat", 3, 'z')  = "bat"
8180    * StringUtils.leftPad("bat", 5, 'z')  = "zzbat"
8181    * StringUtils.leftPad("bat", 1, 'z')  = "bat"
8182    * StringUtils.leftPad("bat", -1, 'z') = "bat"
8183    * </pre>
8184    *
8185    * @param str  the String to pad out, may be null
8186    * @param size  the size to pad to
8187    * @param padChar  the character to pad with
8188    * @return left padded String or original String if no padding is necessary,
8189    *  <code>null</code> if null String input
8190    * @since 2.0
8191    */
8192   public static String leftPad(String str, int size, char padChar) {
8193     if (str == null) {
8194       return null;
8195     }
8196     int pads = size - str.length();
8197     if (pads <= 0) {
8198       return str; // returns original String when possible
8199     }
8200     if (pads > PAD_LIMIT) {
8201       return leftPad(str, size, String.valueOf(padChar));
8202     }
8203     return padding(pads, padChar).concat(str);
8204   }
8205 
8206   /**
8207    * <p>Left pad a String with a specified String.</p>
8208    *
8209    * <p>Pad to a size of <code>size</code>.</p>
8210    *
8211    * <pre>
8212    * StringUtils.leftPad(null, *, *)      = null
8213    * StringUtils.leftPad("", 3, "z")      = "zzz"
8214    * StringUtils.leftPad("bat", 3, "yz")  = "bat"
8215    * StringUtils.leftPad("bat", 5, "yz")  = "yzbat"
8216    * StringUtils.leftPad("bat", 8, "yz")  = "yzyzybat"
8217    * StringUtils.leftPad("bat", 1, "yz")  = "bat"
8218    * StringUtils.leftPad("bat", -1, "yz") = "bat"
8219    * StringUtils.leftPad("bat", 5, null)  = "  bat"
8220    * StringUtils.leftPad("bat", 5, "")    = "  bat"
8221    * </pre>
8222    *
8223    * @param str  the String to pad out, may be null
8224    * @param size  the size to pad to
8225    * @param padStr  the String to pad with, null or empty treated as single space
8226    * @return left padded String or original String if no padding is necessary,
8227    *  <code>null</code> if null String input
8228    */
8229   public static String leftPad(String str, int size, String padStr) {
8230     if (str == null) {
8231       return null;
8232     }
8233     if (isEmpty(padStr)) {
8234       padStr = " ";
8235     }
8236     int padLen = padStr.length();
8237     int strLen = str.length();
8238     int pads = size - strLen;
8239     if (pads <= 0) {
8240       return str; // returns original String when possible
8241     }
8242     if (padLen == 1 && pads <= PAD_LIMIT) {
8243       return leftPad(str, size, padStr.charAt(0));
8244     }
8245 
8246     if (pads == padLen) {
8247       return padStr.concat(str);
8248     } else if (pads < padLen) {
8249       return padStr.substring(0, pads).concat(str);
8250     } else {
8251       char[] padding = new char[pads];
8252       char[] padChars = padStr.toCharArray();
8253       for (int i = 0; i < pads; i++) {
8254         padding[i] = padChars[i % padLen];
8255       }
8256       return new String(padding).concat(str);
8257     }
8258   }
8259 
8260   /**
8261    * convert an exception to a runtime exception
8262    * @param e
8263    */
8264   public static void convertToRuntimeException(Exception e) {
8265     if (e instanceof RuntimeException) {
8266       throw (RuntimeException)e;
8267     }
8268     throw new RuntimeException(e.getMessage(), e);
8269   }
8270 
8271   /**
8272    * <p>Gets the substring before the first occurrence of a separator.
8273    * The separator is not returned.</p>
8274    *
8275    * <p>A <code>null</code> string input will return <code>null</code>.
8276    * An empty ("") string input will return the empty string.
8277    * A <code>null</code> separator will return the input string.</p>
8278    *
8279    * <pre>
8280    * StringUtils.substringBefore(null, *)      = null
8281    * StringUtils.substringBefore("", *)        = ""
8282    * StringUtils.substringBefore("abc", "a")   = ""
8283    * StringUtils.substringBefore("abcba", "b") = "a"
8284    * StringUtils.substringBefore("abc", "c")   = "ab"
8285    * StringUtils.substringBefore("abc", "d")   = "abc"
8286    * StringUtils.substringBefore("abc", "")    = ""
8287    * StringUtils.substringBefore("abc", null)  = "abc"
8288    * </pre>
8289    *
8290    * @param str  the String to get a substring from, may be null
8291    * @param separator  the String to search for, may be null
8292    * @return the substring before the first occurrence of the separator,
8293    *  <code>null</code> if null String input
8294    * @since 2.0
8295    */
8296   public static String substringBefore(String str, String separator) {
8297     if (isEmpty(str) || separator == null) {
8298       return str;
8299     }
8300     if (separator.length() == 0) {
8301       return EMPTY;
8302     }
8303     int pos = str.indexOf(separator);
8304     if (pos == -1) {
8305       return str;
8306     }
8307     return str.substring(0, pos);
8308   }
8309 
8310   /**
8311    * <p>Gets the substring after the first occurrence of a separator.
8312    * The separator is not returned.</p>
8313    *
8314    * <p>A <code>null</code> string input will return <code>null</code>.
8315    * An empty ("") string input will return the empty string.
8316    * A <code>null</code> separator will return the empty string if the
8317    * input string is not <code>null</code>.</p>
8318    *
8319    * <pre>
8320    * StringUtils.substringAfter(null, *)      = null
8321    * StringUtils.substringAfter("", *)        = ""
8322    * StringUtils.substringAfter(*, null)      = ""
8323    * StringUtils.substringAfter("abc", "a")   = "bc"
8324    * StringUtils.substringAfter("abcba", "b") = "cba"
8325    * StringUtils.substringAfter("abc", "c")   = ""
8326    * StringUtils.substringAfter("abc", "d")   = ""
8327    * StringUtils.substringAfter("abc", "")    = "abc"
8328    * </pre>
8329    *
8330    * @param str  the String to get a substring from, may be null
8331    * @param separator  the String to search for, may be null
8332    * @return the substring after the first occurrence of the separator,
8333    *  <code>null</code> if null String input
8334    * @since 2.0
8335    */
8336   public static String substringAfter(String str, String separator) {
8337     if (isEmpty(str)) {
8338       return str;
8339     }
8340     if (separator == null) {
8341       return EMPTY;
8342     }
8343     int pos = str.indexOf(separator);
8344     if (pos == -1) {
8345       return EMPTY;
8346     }
8347     return str.substring(pos + separator.length());
8348   }
8349 
8350   /**
8351    * <p>Gets the substring before the last occurrence of a separator.
8352    * The separator is not returned.</p>
8353    *
8354    * <p>A <code>null</code> string input will return <code>null</code>.
8355    * An empty ("") string input will return the empty string.
8356    * An empty or <code>null</code> separator will return the input string.</p>
8357    *
8358    * <pre>
8359    * StringUtils.substringBeforeLast(null, *)      = null
8360    * StringUtils.substringBeforeLast("", *)        = ""
8361    * StringUtils.substringBeforeLast("abcba", "b") = "abc"
8362    * StringUtils.substringBeforeLast("abc", "c")   = "ab"
8363    * StringUtils.substringBeforeLast("a", "a")     = ""
8364    * StringUtils.substringBeforeLast("a", "z")     = "a"
8365    * StringUtils.substringBeforeLast("a", null)    = "a"
8366    * StringUtils.substringBeforeLast("a", "")      = "a"
8367    * </pre>
8368    *
8369    * @param str  the String to get a substring from, may be null
8370    * @param separator  the String to search for, may be null
8371    * @return the substring before the last occurrence of the separator,
8372    *  <code>null</code> if null String input
8373    * @since 2.0
8374    */
8375   public static String substringBeforeLast(String str, String separator) {
8376     if (isEmpty(str) || isEmpty(separator)) {
8377       return str;
8378     }
8379     int pos = str.lastIndexOf(separator);
8380     if (pos == -1) {
8381       return str;
8382     }
8383     return str.substring(0, pos);
8384   }
8385 
8386   /**
8387    * <p>Gets the substring after the last occurrence of a separator.
8388    * The separator is not returned.</p>
8389    *
8390    * <p>A <code>null</code> string input will return <code>null</code>.
8391    * An empty ("") string input will return the empty string.
8392    * An empty or <code>null</code> separator will return the empty string if
8393    * the input string is not <code>null</code>.</p>
8394    *
8395    * <pre>
8396    * StringUtils.substringAfterLast(null, *)      = null
8397    * StringUtils.substringAfterLast("", *)        = ""
8398    * StringUtils.substringAfterLast(*, "")        = ""
8399    * StringUtils.substringAfterLast(*, null)      = ""
8400    * StringUtils.substringAfterLast("abc", "a")   = "bc"
8401    * StringUtils.substringAfterLast("abcba", "b") = "a"
8402    * StringUtils.substringAfterLast("abc", "c")   = ""
8403    * StringUtils.substringAfterLast("a", "a")     = ""
8404    * StringUtils.substringAfterLast("a", "z")     = ""
8405    * </pre>
8406    *
8407    * @param str  the String to get a substring from, may be null
8408    * @param separator  the String to search for, may be null
8409    * @return the substring after the last occurrence of the separator,
8410    *  <code>null</code> if null String input
8411    * @since 2.0
8412    */
8413   public static String substringAfterLast(String str, String separator) {
8414     if (isEmpty(str)) {
8415       return str;
8416     }
8417     if (isEmpty(separator)) {
8418       return EMPTY;
8419     }
8420     int pos = str.lastIndexOf(separator);
8421     if (pos == -1 || pos == (str.length() - separator.length())) {
8422       return EMPTY;
8423     }
8424     return str.substring(pos + separator.length());
8425   }
8426 
8427   /**
8428    * get the value from the argMap, throw exception if not there and required
8429    * @param argMap
8430    * @param argMapNotUsed 
8431    * @param key
8432    * @param required
8433    * @param defaultValue 
8434    * @return the value or null or exception
8435    */
8436   public static Integer argMapInteger(Map<String, String> argMap, Map<String, String> argMapNotUsed, 
8437       String key, boolean required, Integer defaultValue) {
8438     String argString = argMapString(argMap, argMapNotUsed, key, required);
8439 
8440     if (isBlank(argString) && required) {
8441       throw new RuntimeException("Argument '--" + key + "' is required, but not specified.  e.g. --" + key + "=5");
8442     }
8443     if (isBlank(argString)) {
8444       if (defaultValue != null) {
8445         return defaultValue;
8446       }
8447       return null;
8448     }
8449     return intValue(argString);
8450   }
8451 
8452   /**
8453    * null safe convert from util date to sql date
8454    * @param date
8455    * @return the sql date
8456    */
8457   public static java.sql.Date toSqlDate(Date date) {
8458     if (date == null) {
8459       return null;
8460     }
8461     return new java.sql.Date(date.getTime());
8462   }
8463 
8464   /**
8465    * <p>Find the index of the given object in the array.</p>
8466    *
8467    * <p>This method returns <code>-1</code> if <code>null</code> array input.</p>
8468    * 
8469    * @param array  the array to search through for the object, may be <code>null</code>
8470    * @param objectToFind  the object to find, may be <code>null</code>
8471    * @return the index of the object within the array, 
8472    *  <code>-1</code> if not found or <code>null</code> array input
8473    */
8474   public static int indexOf(Object[] array, Object objectToFind) {
8475     return indexOf(array, objectToFind, 0);
8476   }
8477 
8478   /**
8479    * <p>Checks if the object is in the given array.</p>
8480    *
8481    * <p>The method returns <code>false</code> if a <code>null</code> array is passed in.</p>
8482    * 
8483    * @param array  the array to search through
8484    * @param objectToFind  the object to find
8485    * @return <code>true</code> if the array contains the object
8486    */
8487   public static boolean contains(Object[] array, Object objectToFind) {
8488     return indexOf(array, objectToFind) != -1;
8489   }
8490 
8491   /**
8492    * <p>Find the index of the given object in the array starting at the given index.</p>
8493    *
8494    * <p>This method returns <code>-1</code> if <code>null</code> array input.</p>
8495    *
8496    * <p>A negative startIndex is treated as zero. A startIndex larger than the array
8497    * length will return <code>-1</code>.</p>
8498    * 
8499    * @param array  the array to search through for the object, may be <code>null</code>
8500    * @param objectToFind  the object to find, may be <code>null</code>
8501    * @param startIndex  the index to start searching at
8502    * @return the index of the object within the array starting at the index,
8503    *  <code>-1</code> if not found or <code>null</code> array input
8504    */
8505   public static int indexOf(Object[] array, Object objectToFind, int startIndex) {
8506     if (array == null) {
8507       return -1;
8508     }
8509     if (startIndex < 0) {
8510       startIndex = 0;
8511     }
8512     if (objectToFind == null) {
8513       for (int i = startIndex; i < array.length; i++) {
8514         if (array[i] == null) {
8515           return i;
8516         }
8517       }
8518     } else {
8519       for (int i = startIndex; i < array.length; i++) {
8520         if (objectToFind.equals(array[i])) {
8521           return i;
8522         }
8523       }
8524     }
8525     return -1;
8526   }
8527 
8528   /**
8529    * Note, this is 
8530    * web service format string
8531    */
8532   private static final String WS_DATE_FORMAT = "yyyy/MM/dd HH:mm:ss.SSS";
8533 
8534   /**
8535    * Note, this is 
8536    * web service format string
8537    */
8538   private static final String WS_DATE_FORMAT2 = "yyyy/MM/dd_HH:mm:ss.SSS";
8539 
8540   /**
8541    * convert a date to a string using the standard web service pattern
8542    * yyyy/MM/dd HH:mm:ss.SSS Note that HH is 0-23
8543    * 
8544    * @param date
8545    * @return the string, or null if the date is null
8546    */
8547   public static String dateToString(Date date) {
8548     if (date == null) {
8549       return null;
8550     }
8551     SimpleDateFormat simpleDateFormat = new SimpleDateFormat(WS_DATE_FORMAT);
8552     return simpleDateFormat.format(date);
8553   }
8554 
8555   /**
8556    * convert a string to a date using the standard web service pattern Note
8557    * that HH is 0-23
8558    * 
8559    * @param dateString
8560    * @return the string, or null if the date was null
8561    */
8562   public static Date stringToDate(String dateString) {
8563     if (isBlank(dateString)) {
8564       return null;
8565     }
8566     SimpleDateFormat simpleDateFormat = new SimpleDateFormat(WS_DATE_FORMAT);
8567     try {
8568       return simpleDateFormat.parse(dateString);
8569     } catch (ParseException e) {
8570       SimpleDateFormat simpleDateFormat2 = new SimpleDateFormat(WS_DATE_FORMAT2);
8571       try {
8572         return simpleDateFormat2.parse(dateString);
8573       } catch (ParseException e2) {
8574         throw new RuntimeException("Cannot convert '" + dateString
8575             + "' to a date based on format: " + WS_DATE_FORMAT, e);
8576       }
8577     }
8578   }
8579 
8580   /**
8581    * match regex pattern yyyy-mm-dd or yyyy/mm/dd
8582    */
8583   private static Pattern datePattern_yyyy_mm_dd = Pattern.compile("^(\\d{4})[^\\d]+(\\d{1,2})[^\\d]+(\\d{1,2})$");
8584   
8585   /**
8586    * match regex pattern dd-mon-yyyy or dd/mon/yyyy
8587    */
8588   private static Pattern datePattern_dd_mon_yyyy = Pattern.compile("^(\\d{1,2})[^\\d]+([a-zA-Z]{3,15})[^\\d]+(\\d{4})$");
8589   
8590   /**
8591    * match regex pattern yyyy-mm-dd hh:mm:ss or yyyy/mm/dd hh:mm:ss
8592    */
8593   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})$");
8594   
8595   /**
8596    * match regex pattern dd-mon-yyyy hh:mm:ss or dd/mon/yyyy hh:mm:ss
8597    */
8598   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})$");
8599   
8600   /**
8601    * match regex pattern yyyy-mm-dd hh:mm:ss.SSS or yyyy/mm/dd hh:mm:ss.SSS
8602    */
8603   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})$");
8604   
8605   /**
8606    * match regex pattern dd-mon-yyyy hh:mm:ss.SSS or dd/mon/yyyy hh:mm:ss.SSS
8607    */
8608   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})$");
8609   
8610   /**
8611    * take as input:
8612    * yyyy/mm/dd
8613    * yyyy-mm-dd
8614    * dd-mon-yyyy
8615    * yyyy/mm/dd hh:mm:ss
8616    * dd-mon-yyyy hh:mm:ss
8617    * yyyy/mm/dd hh:mm:ss.SSS
8618    * dd-mon-yyyy hh:mm:ss.SSS
8619    * @param input
8620    * @return the date
8621    */
8622   public static Date stringToDate2(String input) {
8623     
8624     if (isBlank(input)) {
8625       return null;
8626     }
8627     input = input.trim();
8628     Matcher matcher = null;
8629     
8630     int month = 0;
8631     int day = 0;
8632     int year = 0;
8633     int hour = 0;
8634     int minute = 0;
8635     int second = 0;
8636     int milli = 0;
8637     
8638     boolean foundMatch = false;
8639 
8640     //yyyy/mm/dd
8641     if (!foundMatch) {
8642       matcher = datePattern_yyyy_mm_dd.matcher(input);
8643       if (matcher.matches()) {
8644         year = intValue(matcher.group(1));
8645         month =  intValue(matcher.group(2));
8646         day = intValue(matcher.group(3));
8647         foundMatch = true;
8648       }
8649     }
8650     
8651     //dd-mon-yyyy
8652     if (!foundMatch) {
8653       matcher = datePattern_dd_mon_yyyy.matcher(input);
8654       if (matcher.matches()) {
8655         day = intValue(matcher.group(1));
8656         month =  monthInt(matcher.group(2));
8657         year = intValue(matcher.group(3));
8658         foundMatch = true;
8659       }
8660     }
8661     
8662     //yyyy/mm/dd hh:mm:ss
8663     if (!foundMatch) {
8664       matcher = datePattern_yyyy_mm_dd_hhmmss.matcher(input);
8665       if (matcher.matches()) {
8666         year = intValue(matcher.group(1));
8667         month =  intValue(matcher.group(2));
8668         day = intValue(matcher.group(3));
8669         hour = intValue(matcher.group(4));
8670         minute = intValue(matcher.group(5));
8671         second = intValue(matcher.group(6));
8672         foundMatch = true;
8673       }      
8674     }
8675     
8676     //dd-mon-yyyy hh:mm:ss
8677     if (!foundMatch) {
8678       matcher = datePattern_dd_mon_yyyy_hhmmss.matcher(input);
8679       if (matcher.matches()) {
8680         day = intValue(matcher.group(1));
8681         month =  monthInt(matcher.group(2));
8682         year = intValue(matcher.group(3));
8683         hour = intValue(matcher.group(4));
8684         minute = intValue(matcher.group(5));
8685         second = intValue(matcher.group(6));
8686         foundMatch = true;
8687       }
8688     }
8689     
8690     //yyyy/mm/dd hh:mm:ss.SSS
8691     if (!foundMatch) {
8692       matcher = datePattern_yyyy_mm_dd_hhmmss_SSS.matcher(input);
8693       if (matcher.matches()) {
8694         year = intValue(matcher.group(1));
8695         month =  intValue(matcher.group(2));
8696         day = intValue(matcher.group(3));
8697         hour = intValue(matcher.group(4));
8698         minute = intValue(matcher.group(5));
8699         second = intValue(matcher.group(6));
8700         milli = intValue(matcher.group(7));
8701         foundMatch = true;
8702       }      
8703     }
8704     
8705     //dd-mon-yyyy hh:mm:ss.SSS
8706     if (!foundMatch) {
8707       matcher = datePattern_dd_mon_yyyy_hhmmss_SSS.matcher(input);
8708       if (matcher.matches()) {
8709         day = intValue(matcher.group(1));
8710         month =  monthInt(matcher.group(2));
8711         year = intValue(matcher.group(3));
8712         hour = intValue(matcher.group(4));
8713         minute = intValue(matcher.group(5));
8714         second = intValue(matcher.group(6));
8715         milli = intValue(matcher.group(7));
8716         foundMatch = true;
8717       }
8718     }
8719     
8720     Calendar calendar = Calendar.getInstance();
8721     calendar.set(Calendar.YEAR, year);
8722     calendar.set(Calendar.MONTH, month-1);
8723     calendar.set(Calendar.DAY_OF_MONTH, day);
8724     calendar.set(Calendar.HOUR_OF_DAY, hour);
8725     calendar.set(Calendar.MINUTE, minute);
8726     calendar.set(Calendar.SECOND, second);
8727     calendar.set(Calendar.MILLISECOND, milli);
8728     return calendar.getTime();
8729   }
8730 
8731   /**
8732    * convert a month string to an int (1 indexed).
8733    * e.g. if input is feb or Feb or february or February return 2
8734    * @param mon
8735    * @return the month
8736    */
8737   public static int monthInt(String mon) {
8738     
8739     if (!isBlank(mon)) {
8740       mon = mon.toLowerCase();
8741       
8742       if (equals(mon, "jan") || equals(mon, "january")) {
8743         return 1;
8744       }
8745       
8746       if (equals(mon, "feb") || equals(mon, "february")) {
8747         return 2;
8748       }
8749       
8750       if (equals(mon, "mar") || equals(mon, "march")) {
8751         return 3;
8752       }
8753       
8754       if (equals(mon, "apr") || equals(mon, "april")) {
8755         return 4;
8756       }
8757       
8758       if (equals(mon, "may")) {
8759         return 5;
8760       }
8761       
8762       if (equals(mon, "jun") || equals(mon, "june")) {
8763         return 6;
8764       }
8765       
8766       if (equals(mon, "jul") || equals(mon, "july")) {
8767         return 7;
8768       }
8769       
8770       if (equals(mon, "aug") || equals(mon, "august")) {
8771         return 8;
8772       }
8773       
8774       if (equals(mon, "sep") || equals(mon, "september")) {
8775         return 9;
8776       }
8777       
8778       if (equals(mon, "oct") || equals(mon, "october")) {
8779         return 10;
8780       }
8781       
8782       if (equals(mon, "nov") || equals(mon, "november")) {
8783         return 11;
8784       }
8785       
8786       if (equals(mon, "dec") || equals(mon, "december")) {
8787         return 12;
8788       }
8789       
8790     }
8791     
8792     throw new RuntimeException("Invalid month: " + mon);
8793   }
8794 
8795   /**
8796    * override map for properties in thread local to be used in a web server or the like, based on property file name
8797    * @param propertiesFileName 
8798    * @return the override map
8799    */
8800   public static Map<String, String> propertiesThreadLocalOverrideMap(String propertiesFileName) {
8801     Map<String, Map<String, String>> overrideMap = propertiesThreadLocalOverrideMap.get();
8802     if (overrideMap == null) {
8803       overrideMap = new HashMap<String, Map<String, String>>();
8804       propertiesThreadLocalOverrideMap.set(overrideMap);
8805     }
8806     Map<String, String> propertiesOverrideMap = overrideMap.get(propertiesFileName);
8807     if (propertiesOverrideMap == null) {
8808       propertiesOverrideMap = new HashMap<String, String>();
8809       overrideMap.put(propertiesFileName, propertiesOverrideMap);
8810     }
8811     return propertiesOverrideMap;
8812   }
8813 
8814   
8815   /**
8816    * This will execute a command, and split spaces for args (might not be what
8817    * you want if you are using quotes)
8818    * 
8819    * @param command
8820    */
8821   public static void execCommand(String command) {
8822     String[] args = splitTrim(command, " ");
8823     execCommand(args);
8824   }
8825 
8826   /**
8827    * Gobble up a stream from a runtime
8828    * @author mchyzer
8829    * @param <V> 
8830    */
8831   private static class StreamGobbler<V> implements Callable<V> {
8832     
8833     /** stream to read */
8834     InputStream inputStream;
8835     
8836     /** where to put the result */
8837     String resultString;
8838 
8839     /** type of the output for logging purposes */
8840     String type;
8841     
8842     /** command to log */
8843     String command;
8844     /**
8845      * construct
8846      * @param is
8847      * @param theType 
8848      * @param theCommand
8849      */
8850     StreamGobbler(InputStream is, String theType, String theCommand) {
8851       this.inputStream = is;
8852       this.type = theType;
8853       this.command = theCommand;
8854     }
8855 
8856     /**
8857      * get the string result
8858      * @return the result
8859      */
8860     public String getResultString() {
8861       return this.resultString;
8862     }
8863 
8864     // @Override
8865     public V call() throws Exception {
8866       try {
8867         
8868         StringWriter stringWriter = new StringWriter();
8869         copy(this.inputStream, stringWriter);
8870         this.resultString = stringWriter.toString();
8871 
8872       } catch (Exception e) {
8873 
8874         throw new RuntimeException(e);
8875 
8876       }
8877       return null;
8878     }
8879   }
8880   
8881   /**
8882    * <pre>This will execute a command (with args). In this method, 
8883    * if the exit code of the command is not zero, an exception will be thrown.
8884    * Example call: execCommand(new String[]{"/bin/bash", "-c", "cd /someFolder; runSomeScript.sh"}, true);
8885    * </pre>
8886    * @param arguments are the commands
8887    * @return the output text of the command.
8888    */
8889   public static CommandResult execCommand(String[] arguments) {
8890     return execCommand(arguments, true);
8891   }
8892   
8893   /**
8894    * threadpool
8895    */
8896   private static ExecutorService executorService = Executors.newCachedThreadPool(new DaemonThreadFactory());
8897 
8898 
8899   /**
8900    * 
8901    * @return executor service
8902    */
8903   public static ExecutorService retrieveExecutorService() {
8904     return executorService;
8905   }
8906   
8907   /**
8908    * <pre>This will execute a command (with args). Under normal operation, 
8909    * if the exit code of the command is not zero, an exception will be thrown.
8910    * If the parameter exceptionOnExitValueNeZero is set to true, the 
8911    * results of the call will be returned regardless of the exit status.
8912    * Example call: execCommand(new String[]{"/bin/bash", "-c", "cd /someFolder; runSomeScript.sh"}, true);
8913    * </pre>
8914    * @param arguments are the commands
8915    * @param exceptionOnExitValueNeZero if this is set to false, the 
8916    * results of the call will be returned regardless of the exit status
8917    * @return the output text of the command, and the error and return code if exceptionOnExitValueNeZero is false.
8918    */
8919   public static CommandResult execCommand(String[] arguments, boolean exceptionOnExitValueNeZero) {
8920     Process process = null;
8921 
8922     StringBuilder commandBuilder = new StringBuilder();
8923     for (int i = 0; i < arguments.length; i++) {
8924       commandBuilder.append(arguments[i]).append(" ");
8925     }
8926     String command = commandBuilder.toString();
8927     StreamGobbler<Object> outputGobbler = null;
8928     StreamGobbler<Object> errorGobbler = null;
8929     try {
8930       process = Runtime.getRuntime().exec(arguments);
8931 
8932       outputGobbler = new StreamGobbler<Object>(process.getInputStream(), ".out", command);
8933       errorGobbler = new StreamGobbler<Object>(process.getErrorStream(), ".err", command);
8934 
8935       Future<Object> futureOutput = retrieveExecutorService().submit(outputGobbler);
8936       Future<Object> futureError = retrieveExecutorService().submit(errorGobbler);
8937       
8938       try {
8939         process.waitFor();
8940       } finally {
8941         
8942         //finish running these threads
8943         try {
8944           futureOutput.get();
8945         } finally {
8946           //ignore if cant get
8947         }
8948         try {
8949           futureError.get();
8950         } finally {
8951           //ignore if cant get
8952         }
8953       }
8954     } catch (Exception e) {
8955       throw new RuntimeException("Error running command", e);
8956     } finally {
8957       try {
8958         process.destroy();
8959       } catch (Exception e) {
8960       }
8961     }
8962     
8963     //was not successful???
8964     if (process.exitValue() != 0 && exceptionOnExitValueNeZero) {
8965       String message = "Process exit status=" + process.exitValue() + ": out: " + 
8966         (outputGobbler == null ? null : outputGobbler.getResultString())
8967         + ", err: " + (errorGobbler == null ? null : errorGobbler.getResultString());
8968       throw new RuntimeException(message);
8969     }
8970 
8971     int exitValue = process.exitValue();
8972     return new CommandResult(outputGobbler.getResultString(), errorGobbler.getResultString(), exitValue);
8973   }
8974 
8975   
8976   /**
8977    * The results of executing a command.
8978    */
8979   public static class CommandResult{
8980     /**
8981      * If any error text was generated by the call, it will be set here.
8982      */
8983     private String errorText;
8984     
8985     /**
8986      * If any output text was generated by the call, it will be set here.
8987      */
8988     private String outputText;
8989     
8990     /**
8991      * If any exit code was generated by the call, it will be set here.
8992      */
8993     private int exitCode;
8994     
8995     
8996     /**
8997      * Create a container to hold the results of an execution.
8998      * @param _errorText
8999      * @param _outputText
9000      * @param _exitCode
9001      */
9002     public CommandResult(String _errorText, String _outputText, int _exitCode){
9003       this.errorText = _errorText;
9004       this.outputText = _outputText;
9005       this.exitCode = _exitCode;
9006     }
9007 
9008 
9009     
9010     /**
9011      * If any error text was generated by the call, it will be set here.
9012      * @return the errorText
9013      */
9014     public String getErrorText() {
9015       return this.errorText;
9016     }
9017 
9018 
9019     
9020     /**
9021      * If any output text was generated by the call, it will be set here.
9022      * @return the outputText
9023      */
9024     public String getOutputText() {
9025       return this.outputText;
9026     }
9027 
9028 
9029     
9030     /**
9031      * If any exit code was generated by the call, it will be set here.
9032      * @return the exitCode
9033      */
9034     public int getExitCode() {
9035       return this.exitCode;
9036     }
9037     
9038     
9039     
9040   }
9041 
9042   /**
9043    * thread factory with daemon threads so the JVM exits
9044    * @author mchyzer
9045    *
9046    */
9047   private static class DaemonThreadFactory implements ThreadFactory {
9048     private ThreadFactory threadFactory = Executors.defaultThreadFactory();
9049   
9050     @Override
9051     public Thread newThread(Runnable r) {
9052       Thread thread = threadFactory.newThread(r);
9053       thread.setDaemon(true);
9054       return thread;
9055     }
9056   
9057   }
9058 
9059   /**
9060    * serialize an object to a file (create parent dir if necessary)
9061    * @param object
9062    * @param file
9063    */
9064   public static void serializeObjectToFile(Serializable object, File file) {
9065 
9066     //delete, make sure parents are there
9067     deleteCreateFile(file);
9068 
9069     FileOutputStream fos = null;
9070     ObjectOutputStream out = null;
9071     try {
9072       fos = new FileOutputStream(file);
9073       out = new ObjectOutputStream(fos);
9074       out.writeObject(object);
9075     } catch(IOException ex) {
9076       //we had a problem, dont leave the file partway created...
9077       closeQuietly(out);
9078       out = null;
9079       deleteFile(file);
9080       throw new RuntimeException("Error writing file: " + absolutePath(file)
9081           + ", " + className(object), ex);
9082     } finally {
9083       closeQuietly(out);
9084     }
9085     
9086   }
9087 
9088   /**
9089    * unserialize an object from a file if it exists
9090    * @param file
9091    * @param nullIfException true if null should be returned instead of exception
9092    * @param deleteFileOnException true if file should be deleted on exception
9093    * @return the object or null
9094    */
9095   public static Serializable unserializeObjectFromFile(File file, boolean nullIfException,
9096       boolean deleteFileOnException) {
9097     
9098     if (!file.exists() || file.length() == 0) {
9099       return null;
9100     }
9101     
9102     FileInputStream fis = null;
9103     ObjectInputStream ois = null;
9104     try {
9105       fis = new FileInputStream(file);
9106       ois = new ObjectInputStream(fis);
9107       return (Serializable)ois.readObject();
9108     } catch(Exception ex) {
9109       String error = "Error writing file: " + absolutePath(file);
9110       if (!nullIfException) {
9111         throw new RuntimeException(error, ex);
9112       }
9113       //maybe clear the file if problem
9114       if (deleteFileOnException) {
9115         //close before deleting
9116         closeQuietly(ois);
9117         ois = null;
9118         deleteFile(file);
9119       }
9120       return null;
9121     } finally {
9122       closeQuietly(ois);
9123     }
9124     
9125   }
9126   
9127   /**
9128    * delete and create a new file.  If its a directory, delete, and create a new dir.
9129    * 
9130    * @param file
9131    *          is the file to delete and create
9132    */
9133   public static void deleteCreateFile(File file) {
9134 
9135     deleteFile(file);
9136 
9137     createParentDirectories(file);
9138 
9139     try {
9140       if (!file.createNewFile()) {
9141         throw new IOException("createNewFile returned false: ");
9142       }
9143     } catch (IOException ioe) {
9144       throw new RuntimeException("Couldnt create new file: " + file.toString(), ioe);
9145     }
9146 
9147   }
9148 
9149   /**
9150    * Delete a file, throw exception if cannot
9151    * @param file
9152    */
9153   public static void deleteFile(File file) {
9154     //delete and create
9155     if (file != null && file.exists()) {
9156 
9157       if (file.isDirectory()) {
9158         deleteRecursiveDirectory(file.getAbsolutePath());
9159       } else if (!file.delete()) {
9160         throw new RuntimeException("Couldnt delete file: " + file.toString());
9161       }
9162     }
9163   }
9164 
9165   /**
9166    * copy a file to a new file
9167    * @param fromFile
9168    * @param toFile
9169    */
9170   public static void copy(File fromFile, File toFile) {
9171     if (toFile.exists()) {
9172       deleteFile(toFile);
9173     }
9174     FileInputStream fromFileStream = null;
9175     FileOutputStream toFileStream = null;
9176     try {
9177       fromFileStream = new FileInputStream(fromFile);
9178       toFileStream = new FileOutputStream(toFile);
9179       copy(fromFileStream, toFileStream);
9180     } catch (Exception e) {
9181       throw new RuntimeException("Problem copying file: " + fromFile.getAbsolutePath() 
9182           + " to file: " + toFile.getAbsolutePath());
9183     }
9184     
9185   }
9186 
9187   /**
9188    * rename a file to another file and throw runtime exception if not ok
9189    * @param fromFile
9190    * @param toFile
9191    */
9192   public static void renameTo(File fromFile, File toFile) {
9193 
9194     if (!fromFile.renameTo(toFile)) {
9195       throw new RuntimeException("Cannot rename file: '" + fromFile.getAbsolutePath() 
9196           + "', to file: '" + toFile.getAbsolutePath() + "'");
9197     }
9198 
9199   }
9200 
9201   /**
9202    * clear out all files recursively in a directory, including the directory
9203    * itself
9204    * @param dirName
9205    * 
9206    * @throws RuntimeException
9207    *           when something goes wrong
9208    */
9209   public static void deleteRecursiveDirectory(String dirName) {
9210     //delete all files in the directory
9211     File dir = new File(dirName);
9212 
9213     //if it doesnt exist then we are done
9214     if (!dir.exists()) {
9215       return;
9216     }
9217 
9218     //see if its a directory
9219     if (!dir.isDirectory()) {
9220       throw new RuntimeException("The directory: " + dirName + " is not a directory");
9221     }
9222 
9223     //get the files into a vector
9224     File[] allFiles = dir.listFiles();
9225 
9226     //loop through the array
9227     for (int i = 0; i < allFiles.length; i++) {
9228       if (-1 < allFiles[i].getName().indexOf("..")) {
9229         continue; //dont go to the parent directory
9230       }
9231 
9232       if (allFiles[i].isFile()) {
9233         //delete the file
9234         if (!allFiles[i].delete()) {
9235           throw new RuntimeException("Could not delete file: " + allFiles[i].getPath());
9236         }
9237       } else {
9238         //its a directory
9239         deleteRecursiveDirectory(allFiles[i].getPath());
9240       }
9241     }
9242 
9243     //delete the directory itself
9244     if (!dir.delete()) {
9245       throw new RuntimeException("Could not delete directory: " + dir.getPath());
9246     }
9247   }
9248 
9249   /**
9250    * absolute path null safe
9251    * @param file
9252    * @return absolute path null safe
9253    */
9254   public static String absolutePath(File file) {
9255     return file == null ? null : file.getAbsolutePath();
9256   }
9257 
9258 
9259   /**
9260    * pattern to get the file path or resource location for a file
9261    */
9262   private static Pattern fileLocationPattern = Pattern.compile("^(file|classpath)\\s*:(.*)$");
9263   
9264   /**
9265    * file or classpath location
9266    * @param typeAndLocation
9267    * @return the inputstream
9268    */
9269   public static InputStream fileOrClasspathInputstream(String typeAndLocation, String logHint) {
9270     Matcher matcher = fileLocationPattern.matcher(typeAndLocation);
9271     if (!matcher.matches()) {
9272       throw new RuntimeException(logHint + " must start with file: or classpath:");
9273     }
9274     String typeString = matcher.group(1);
9275     String location = trim(matcher.group(2));
9276     
9277     if (equals(typeString, "file")) {
9278       File file = new File(location);
9279       if (!file.exists() || !file.isFile()) {
9280         throw new RuntimeException(logHint + " File does not exist: " + file.getAbsolutePath());
9281       }
9282       try {
9283         return new FileInputStream(file);
9284       } catch (Exception e) {
9285         throw new RuntimeException(logHint + " Problem with file: " + file.getAbsolutePath());
9286       }
9287     } else if (equals(typeString, "classpath")) {
9288       if (!location.startsWith("/")) {
9289         location = "/" + location;
9290       }
9291       try {
9292         return ConfigPropertiesCascadeCommonUtils.class.getResourceAsStream(location);
9293       } catch (Exception e) {
9294         throw new RuntimeException(logHint + " Problem with classpath location: " + location);
9295       }
9296     } else {
9297       throw new RuntimeException(logHint + " Not expecting type string: " + typeString);
9298     }
9299     
9300   }
9301   
9302 
9303 
9304 }