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