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