View Javadoc
1   /**
2    * Copyright 2014 Internet2
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  /**
17   *
18   */
19  package edu.internet2.middleware.grouper.util;
20  
21  import java.beans.PropertyDescriptor;
22  import java.io.BufferedReader;
23  import java.io.Closeable;
24  import java.io.File;
25  import java.io.FileInputStream;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.InputStreamReader;
29  import java.io.OutputStream;
30  import java.io.PrintWriter;
31  import java.io.PushbackInputStream;
32  import java.io.Reader;
33  import java.io.StringWriter;
34  import java.io.UnsupportedEncodingException;
35  import java.io.Writer;
36  import java.lang.annotation.Annotation;
37  import java.lang.reflect.Array;
38  import java.lang.reflect.Constructor;
39  import java.lang.reflect.Field;
40  import java.lang.reflect.InvocationTargetException;
41  import java.lang.reflect.Method;
42  import java.lang.reflect.Modifier;
43  import java.math.BigDecimal;
44  import java.net.InetAddress;
45  import java.net.ServerSocket;
46  import java.net.URL;
47  import java.net.URLDecoder;
48  import java.net.URLEncoder;
49  import java.nio.charset.StandardCharsets;
50  import java.security.CodeSource;
51  import java.security.MessageDigest;
52  import java.security.NoSuchAlgorithmException;
53  import java.sql.Connection;
54  import java.sql.ResultSet;
55  import java.sql.SQLException;
56  import java.sql.Statement;
57  import java.sql.Timestamp;
58  import java.text.DateFormat;
59  import java.text.DecimalFormat;
60  import java.text.Normalizer;
61  import java.text.ParseException;
62  import java.text.SimpleDateFormat;
63  import java.util.ArrayList;
64  import java.util.Arrays;
65  import java.util.Calendar;
66  import java.util.Collection;
67  import java.util.Collections;
68  import java.util.Comparator;
69  import java.util.Date;
70  import java.util.Enumeration;
71  import java.util.HashMap;
72  import java.util.HashSet;
73  import java.util.Iterator;
74  import java.util.LinkedHashMap;
75  import java.util.LinkedHashSet;
76  import java.util.List;
77  import java.util.Map;
78  import java.util.Properties;
79  import java.util.Set;
80  import java.util.TimeZone;
81  import java.util.TreeSet;
82  import java.util.concurrent.Callable;
83  import java.util.concurrent.ExecutorService;
84  import java.util.concurrent.Executors;
85  import java.util.concurrent.Future;
86  import java.util.concurrent.ThreadFactory;
87  import java.util.regex.Matcher;
88  import java.util.regex.Pattern;
89  
90  import javax.xml.stream.XMLStreamException;
91  import javax.xml.stream.XMLStreamWriter;
92  
93  import org.apache.commons.beanutils.PropertyUtils;
94  import org.apache.commons.codec.binary.Base64;
95  import org.apache.commons.io.FileUtils;
96  import org.apache.commons.jexl2.Expression;
97  import org.apache.commons.jexl2.JexlContext;
98  import org.apache.commons.jexl2.JexlEngine;
99  import org.apache.commons.jexl2.JexlException;
100 import org.apache.commons.jexl2.MapContext;
101 import org.apache.commons.jexl2.Script;
102 import org.apache.commons.jexl3.JexlBuilder;
103 import org.apache.commons.jexl3.JxltEngine;
104 import org.apache.commons.jexl3.JxltEngine.Template;
105 import org.apache.commons.lang.StringUtils;
106 import org.apache.commons.lang.exception.ExceptionUtils;
107 import org.apache.commons.lang.exception.Nestable;
108 import org.apache.commons.logging.Log;
109 import org.apache.commons.logging.LogFactory;
110 import org.apache.commons.logging.impl.Log4JLogger;
111 import org.apache.commons.validator.routines.EmailValidator;
112 import org.apache.log4j.Appender;
113 import org.apache.log4j.Category;
114 import org.apache.log4j.ConsoleAppender;
115 import org.apache.log4j.FileAppender;
116 import org.codehaus.groovy.tools.shell.ExitNotification;
117 import org.hibernate.Session;
118 import org.hibernate.Transaction;
119 import org.hibernate.cfg.Configuration;
120 import org.hibernate.resource.transaction.spi.TransactionStatus;
121 import org.ldaptive.io.Hex;
122 
123 import com.fasterxml.jackson.databind.JsonNode;
124 import com.fasterxml.jackson.databind.ObjectMapper;
125 import com.fasterxml.jackson.databind.node.ArrayNode;
126 import com.fasterxml.jackson.databind.node.NullNode;
127 import com.fasterxml.jackson.databind.node.ObjectNode;
128 import com.unboundid.ldap.sdk.DN;
129 import com.unboundid.ldap.sdk.LDAPException;
130 import com.unboundid.ldap.sdk.RDN;
131 
132 import edu.internet2.middleware.grouper.Group;
133 import edu.internet2.middleware.grouper.GrouperSession;
134 import edu.internet2.middleware.grouper.Stem;
135 import edu.internet2.middleware.grouper.StemFinder;
136 import edu.internet2.middleware.grouper.app.gsh.GrouperGroovyRuntime;
137 import edu.internet2.middleware.grouper.app.gsh.GrouperGroovysh;
138 import edu.internet2.middleware.grouper.app.loader.OtherJobScript;
139 import edu.internet2.middleware.grouper.cache.GrouperCache;
140 import edu.internet2.middleware.grouper.cfg.GrouperConfig;
141 import edu.internet2.middleware.grouper.cfg.GrouperHibernateConfig;
142 import edu.internet2.middleware.grouper.exception.ExpressionLanguageMissingVariableException;
143 import edu.internet2.middleware.grouper.hibernate.HibUtils;
144 import edu.internet2.middleware.grouper.hooks.logic.HookVeto;
145 import edu.internet2.middleware.grouper.misc.GrouperCloneable;
146 import edu.internet2.middleware.grouper.misc.GrouperId;
147 import edu.internet2.middleware.grouper.misc.GrouperStartup;
148 import edu.internet2.middleware.grouper.subj.GrouperSubject;
149 import edu.internet2.middleware.grouperClient.collections.MultiKey;
150 import edu.internet2.middleware.grouperClient.util.ExpirableCache;
151 import edu.internet2.middleware.grouperClient.util.GrouperClientUtils;
152 import edu.internet2.middleware.subject.Source;
153 import edu.internet2.middleware.subject.Subject;
154 import edu.internet2.middleware.subject.provider.SourceManager;
155 import javassist.util.proxy.ProxyObject;
156 import net.sf.json.JSONObject;
157 import net.sf.json.JsonConfig;
158 import net.sf.json.util.PropertyFilter;
159 
160 
161 /**
162  * utility methods for grouper.  Generally these are static methods.
163  *
164  * @author mchyzer
165  *
166  */
167 public class GrouperUtil {
168 
169   public static void main(String[] args) throws Exception {
170 
171     GrouperStartup.startup();
172 
173 //    System.out.println(ldapBushyDn("Juicy\\, Fruit:b:c", "cn", "o\\u", true, false));
174 //    System.out.println(ldapBushyDn("Juicy, Fruit:b:c", "cn", "ou", true, false));
175 
176 //    System.out.println(javax.naming.ldap.Rdn.escapeValue("Juicy\\, Fruit:b:c"));
177 //    System.out.println(javax.naming.ldap.Rdn.escapeValue("Juicy, Fruit:b:c"));
178 //    
179 //    String dn = "cn=First\\, Last,ou=whatever,ou=edu";
180 //    String cn = new javax.naming.ldap.LdapName(dn).getRdn(
181 //        new javax.naming.ldap.LdapName(dn).getRdns().size()-1).getValue().toString() ;
182 //    System.out.println(cn);
183     
184 //    ProvisioningGroup grouperProvisioningGroup = new ProvisioningGroup();
185 //    grouperProvisioningGroup.setName("org:flint:app:Flint_AD:service:policy:Flint_AD_Alumni_Folder:Flint_AD_Alumni");
186 //    Map<String, Object> variableMap = new HashMap<String, Object>();
187 //    variableMap.put("grouperProvisioningGroup", grouperProvisioningGroup);
188 
189 //    String result = (String)GrouperUtil.substituteExpressionLanguageScript(
190 //        "${edu.internet2.middleware.grouper.util.GrouperUtil.ldapBushyDn(edu.internet2.middleware.grouper.util.GrouperUtil.stripPrefix(grouperProvisioningGroup.name, 'org:flint:'), 'cn', 'ou', true, false) +  ',OU=Grouper,DC=ads,DC=umflint,DC=net'}"
191 //        , variableMap, true, false, false);
192 
193 //    grouperProvisioningGroup.assignAttributeValue("gidNumber", "123456");
194 //    variableMap.put("targetGroup", grouperProvisioningGroup);
195 //    String result = (String)GrouperUtil.substituteExpressionLanguageScript(
196 //      "${'(&(gidNumber='+targetGroup.retrieveAttributeValue('gidNumber')+')(objectClass=posixGroup))'}"
197 //      , variableMap, true, false, false);
198 
199     
200     
201 //    edu.internet2.middleware.grouper.util.GrouperUtil.substituteExpressionLanguage@10253![33,110]: ''(samAccountLink=' + grouperUtil.ldapFilterEscape(targetEntity.retrieveAttributeValueString('samAccountName')) + ')';' unknown, ambiguous or inaccessible method 
202     
203 //    ProvisioningEntity targetEntity = new ProvisioningEntity();
204 //    targetEntity.assignAttributeValue("samAccountName", "mchyzer");
205 //    variableMap.put("targetEntity", targetEntity);
206     
207 //    String result = (String)GrouperUtil.substituteExpressionLanguageScript(
208 //      "${'(samAccountName=' + edu.internet2.middleware.grouper.util.GrouperUtil.ldapFilterEscape(targetEntity.retrieveAttributeValueString('samAccountName')) + ')'}"
209 //      , variableMap, true, false, false);
210     
211 //    System.out.println(result);
212 
213     // 5341, 3120
214 //    long startMillis = System.currentTimeMillis();
215 //    String result = GrouperUtil.gshRunScript("if (true) {\n  System.err.println('true');\n} else {\n  System.out.println('false');\n}", false);
216 //    System.out.println("Result: " + result);
217 //    System.out.println("Took millis: " + (System.currentTimeMillis() - startMillis));
218 
219 //    final String[] scripts = new String[11];
220 //    Thread[] threads = new Thread[11];
221 //    long mainStart = System.nanoTime();
222 //    for (int i=0;i<11;i++) {
223 //      scripts[i] = "";
224 ////      scripts[i] += GrouperUtil.readResourceIntoString("groovy.profile", false) + "\n";
225 //      
226 //      
227 //      GrouperGroovyInput grouperGroovyInput = new GrouperGroovyInput();
228 //      grouperGroovyInput.assignInputValueObject("i", i);
229 //      int sleepMillis = (int)(Math.random() * 1000);
230 //      grouperGroovyInput.assignInputValueObject("sleepMillis", sleepMillis);
231 //      scripts[i] += "int i = grouperGroovyRuntime.retrieveInputValueInteger(\"i\");\n";
232 //      scripts[i] += "int sleepMillis = grouperGroovyRuntime.retrieveInputValueInteger(\"sleepMillis\");\n";
233 //      scripts[i] += "String script = \"script\" + i;\n";
234 //      scripts[i] += "int count = i;\n";
235 //      scripts[i] += "//GrouperUtil.sleep(sleepMillis);\n";
236 //      scripts[i] += "count++;\n";
237 //      scripts[i] += "grouperGroovyRuntime.debugMap(\"i\", i);\n";
238 //      scripts[i] += "grouperGroovyRuntime.debugMap(\"count\", count);\n";
239 //      scripts[i] += "grouperGroovyRuntime.debugMap(\"script\", script);\n";
240 //      scripts[i] += "grouperGroovyRuntime.println(script + \": \" + count + \": \" + sleepMillis);\n";
241 //      grouperGroovyInput.assignScript(scripts[i].toString());
242 //      GrouperGroovyResult grouperGroovyResult = new GrouperGroovyResult();
243 //
244 //      final int INDEX = i;
245 //      threads[i] = new Thread(new Runnable() {
246 //        
247 //        @Override
248 //        public void run() {
249 //          try {
250 //            GrouperSession.startRootSession();
251 //            
252 //            long start = System.nanoTime();
253 //            GrouperGroovysh.runScript(grouperGroovyInput, grouperGroovyResult);
254 //            System.out.println(grouperGroovyResult.fullOutput());
255 ////            ScriptEngineManager factory = new ScriptEngineManager();
256 ////            ScriptEngine engine = factory.getEngineByName("groovy");
257 ////            CompiledScript compiledScript = ((Compilable)engine).compile(scripts[INDEX]);
258 ////            System.out.println("Compile: " + ((System.nanoTime()-start) / 1000000));
259 ////            start = System.nanoTime();
260 ////            compiledScript.eval();
261 //            System.out.println("Run: " + ((System.nanoTime()-start) / 1000000));
262 //          } catch (Exception e) {
263 //            LOG.error("error", e);
264 //          }
265 //        }
266 //      });
267 //      if (i==0) {
268 //        threads[i].start();
269 //      }
270 //    }
271 //    threads[0].join();
272 //
273 //    mainStart = System.nanoTime();
274 //    for (int i=1;i<11;i++) {
275 //      threads[i].start();
276 //    }
277 //    
278 //    for (int i=1;i<11;i++) {
279 //      threads[i].join();
280 //    }
281 //    System.out.println("All: " + ((System.nanoTime()-mainStart) / 1000000));
282     
283     GrouperSession.startRootSession();
284     OtherJobScripter/OtherJobScript.html#OtherJobScript">OtherJobScript otherJobScript = new OtherJobScript();
285     otherJobScript.execute("OTHER_JOB_deleteNgssWsProxyCache", null);
286     
287   }
288 
289   /**
290    * run a GSH script
291    * @param script
292    * @param lightWeight will use an abbreviated groovysh.profile for faster speed.  built in commands
293    * arent there and imports largely arent there
294    * @return the output
295    */
296   public static String gshRunScript(String script) {
297     return gshRunScript(script, false);
298   }
299 
300   /**
301    * run a GSH script
302    * @param script
303    * @param lightWeight will use an abbreviated groovysh.profile for faster speed.  built in commands
304    * arent there and imports largely arent there
305    * @param inputMap puts variables in a context to be retrieved with GrouperUtil.gshRetrieveInputValueObject(),
306    * GrouperUtil.gshRetrieveInputValueString(), GrouperUtil.gshRetrieveInputValueInteger(), GrouperUtil.gshRetrieveInputValueBoolean()
307    * @return the full output
308    */
309   public static String gshRunScript(String script, boolean lightWeight) {
310     GrouperGroovysh.GrouperGroovyResult grouperGroovyResult = GrouperGroovysh.runScript(script, lightWeight);
311     String output = grouperGroovyResult.fullOutput();
312     return output;
313   }
314 
315   public static void setClear(Set<?> set) {
316     if (set != null) {
317       set.clear();
318     }
319   }
320   
321   /**
322    * in a finally block, take an exception and inject and throw the existing exception, or just throw the finally exception
323    * @param tryException
324    * @param finallyException
325    */
326   public static void exceptionFinallyInjectOrThrow(Exception tryException,
327       Exception finallyException) {
328     if (tryException != null) {
329       GrouperUtil.injectInException(tryException, "\n\n####FINALLY EXCEPTION START####\n\n" + GrouperUtil.getFullStackTrace(finallyException) + "\n\n####FINALLY EXCEPTION END####\n\n");
330     } else {
331       tryException = finallyException;
332     }
333     if (tryException instanceof RuntimeException) {
334       throw (RuntimeException)tryException;
335     }
336     throw new RuntimeException(tryException);
337   }
338 
339 
340   /**
341    * take out accented chars with
342    * grouperUtil.normalize("NFD", groupAttribute).replaceAll("\\p{M}", "")
343    * @param form
344    * @param text
345    * @return the normalized string
346    */
347   public static String normalize(String form, String text) {
348     if (text == null) {
349       return text;
350     }
351     Normalizer.Form formEnum = Normalizer.Form.valueOf(text);
352     return Normalizer.normalize(text, formEnum);
353 
354   }
355 
356 
357   /**
358    * create one set of jexlEngine instances (one per type of setting) so we can cache expressions
359    */
360   private final static Map<MultiKey, JexlEngine> jexlEngines = new HashMap<MultiKey, JexlEngine>();
361   
362   /**
363    * if the jexl engine instances are all initialized completely
364    */
365   private static boolean jexlEnginesInitialized = false;
366   
367   /**
368    * initialize the instances
369    */
370   static {
371     {
372       Boolean silent = true;
373       Boolean lenient = true;
374       final JexlEngine jexlEngine = new JexlEngine();
375       jexlEngine.setSilent(silent);
376       jexlEngine.setLenient(lenient);
377       jexlEngines.put(new MultiKey(silent, lenient), jexlEngine);
378     }
379     {
380       Boolean silent = false;
381       Boolean lenient = true;
382       final JexlEngine jexlEngine = new JexlEngine();
383       jexlEngine.setSilent(silent);
384       jexlEngine.setLenient(lenient);
385       jexlEngines.put(new MultiKey(silent, lenient), jexlEngine);
386     }
387     {
388       Boolean silent = true;
389       Boolean lenient = false;
390       final JexlEngine jexlEngine = new JexlEngine();
391       jexlEngine.setSilent(silent);
392       jexlEngine.setLenient(lenient);
393       jexlEngines.put(new MultiKey(silent, lenient), jexlEngine);
394     }
395     {
396       Boolean silent = false;
397       Boolean lenient = false;
398       final JexlEngine jexlEngine = new JexlEngine();
399       jexlEngine.setSilent(silent);
400       jexlEngine.setLenient(lenient);
401       jexlEngines.put(new MultiKey(silent, lenient), jexlEngine);
402     }
403   }
404   
405   /** override map for properties in thread local to be used in a web server or the like */
406   private static ThreadLocal<Map<String, Map<String, String>>> propertiesThreadLocalOverrideMap = new ThreadLocal<Map<String, Map<String, String>>>();
407 
408   
409   /** used to indicate if we're in code that would be retried if there's a failure */
410   private static ThreadLocal<Boolean> inRetriableCode = new InheritableThreadLocal<Boolean>();
411   
412   /**
413    * @return true if we're in code that would be retried if there's a failure
414    */
415   public static boolean isInRetriableCode() {
416     Boolean threadlocalBoolean = inRetriableCode.get();
417     return threadlocalBoolean != null && threadlocalBoolean;
418   }
419   
420   /**
421    * used to indicate if we're in code that would be retried if there's a failure
422    */
423   public static void threadLocalInRetriableCodeAssign() {
424     inRetriableCode.set(true);
425   }
426 
427   /**
428    * used to indicate if we're in code that would be retried if there's a failure
429    * call within a finally block to remove
430    */
431   public static void threadLocalInRetriableCodeClear() {
432     inRetriableCode.remove();
433   }
434   
435   /**
436    * take email addresses from a textarea and turn them into semi separated
437    * @param emailAddresses
438    * @return string of email addresses
439    */
440   public static String splitTrimEmailAddresses(String emailAddresses) {
441     //this is renamed since the normalize method is difficult to find
442     return normalizeEmailAddresses(emailAddresses);
443   }
444   
445   /**
446    * take email addresses from a textarea and turn them into semi separated
447    * @param emailAddresses can be whitespace, comma, or semi separated
448    * @return the email addresses semi separated
449    */
450   public static String normalizeEmailAddresses(String emailAddresses) {
451     if (emailAddresses == null) {
452       return null;
453     }
454     emailAddresses = StringUtils.replace(emailAddresses, ",", " ");
455     emailAddresses = StringUtils.replace(emailAddresses, ";", " ");
456     emailAddresses = StringUtils.replace(emailAddresses, "\n", " ");
457     emailAddresses = StringUtils.replace(emailAddresses, "\t", " ");
458     emailAddresses = StringUtils.replace(emailAddresses, "\r", " ");
459     emailAddresses = join(splitTrim(emailAddresses, " "), ";");
460     return emailAddresses;
461   }
462 
463   /**
464    *
465    * @param email
466    * @return true if valid, false if not
467    */
468   public static boolean validEmail(String email) {
469     return EmailValidator.getInstance(false, true).isValid(email);
470   }
471 
472   /**
473    * see if a subject has an attribute
474    * @param subject
475    * @param attributeName
476    * @return true if the subject has an attribute
477    */
478   public static boolean subjectHasAttribute(Subject subject, String attributeName) {
479     if (subject == null) {
480       return false;
481     }
482     String attributeValue = subject.getAttributeValue(attributeName);
483     return !isBlank(attributeValue);
484   }
485 
486   /**
487    * match sqlLike string
488    * see if a sql like string matches a real string.
489    * e.g. if the input is a:b:%, and the input is: a:b:test:that, then it returns true
490    * @param sqlMatcher
491    * @param testText
492    * @return true if matches, false if not
493    */
494   public static boolean matchSqlString(String sqlMatcher, String testText) {
495 
496     //first do slashes
497     String regexString = StringUtils.replace(sqlMatcher, "\\", "\\\\");
498 
499     //then do everything else
500     regexString = replace(regexString,
501         new String[]{"$",   "^",   "*",   "(",   ")",   "+",   "[",   "{",   "]",   "}",   "|",   "\"", ".",   "?"},
502         new String[]{"\\$", "\\^", "\\*", "\\(", "\\)", "\\+", "\\[", "\\{", "\\]", "\\}", "\\|", "\"", "\\.", "\\?", });
503 
504     //then do the underscores and percents
505     regexString = StringUtils.replace(regexString, "_", ".");
506     regexString = "^" + StringUtils.replace(regexString, "%", ".*") + "$";
507 
508     Pattern pattern = Pattern.compile(regexString, Pattern.DOTALL);
509     Matcher matcher = pattern.matcher(testText);
510     return matcher.matches();
511   }
512 
513   /**
514    * shorten a set if it is too long
515    * @param <T>
516    * @param theSet
517    * @param maxSize
518    * @return the new set
519    */
520   public static <T> Set<T> setShorten(Set<T> theSet, int maxSize) {
521 
522     if (length(theSet) < maxSize) {
523       return theSet;
524     }
525 
526     //truncate the list
527     Set<T> newList = new LinkedHashSet<T>();
528     int i=0;
529 
530     //TODO test this logic
531     for (T t : theSet) {
532 
533       if (i>=maxSize) {
534         break;
535       }
536 
537       newList.add(t);
538       i++;
539     }
540     return newList;
541   }
542 
543   /**
544    *
545    * @param number e.g. 12345678
546    * @return the string, e.g. 12,345,678
547    */
548   public static String formatNumberWithCommas(Long number) {
549     if (number == null) {
550       return "null";
551     }
552     DecimalFormat df = new DecimalFormat();
553     return df.format(number);
554   }
555 
556   /**
557    * compare null safe
558    * @param first
559    * @param second
560    * @return 0 for equal, 1 for greater, -1 for less
561    */
562   public static int compare(Comparable first, Comparable second) {
563     if (first == second) {
564       return 0;
565     }
566     if (first == null) {
567       return -1;
568     }
569     if (second == null) {
570       return 1;
571     }
572     return first.compareTo(second);
573   }
574 
575   /**
576    * e.g. ('g:gsa', 'jdbc')
577    * @param sources is comma separated source id's
578    * @return the set of sources
579    */
580   public static Set<Source> convertSources(String sources) {
581     if (StringUtils.isBlank(sources)) {
582       return null;
583     }
584     String[] sourceStrings = splitTrim(sources, ",");
585 
586     Set<Source> sourceSet = new HashSet<Source>();
587     for (String source : sourceStrings) {
588       sourceSet.add(SourceManager.getInstance().getSource(source));
589     }
590 
591     return sourceSet;
592   }
593 
594 
595   /**
596    * e.g. ['g:gsa', 'jdbc']
597    * @param sourceIds is an array of source ids
598    * @return the set of sources
599    */
600   public static Set<Source> convertSources(String[] sourceIds) {
601     if (length(sourceIds) == 0) {
602       return null;
603     }
604     Set<Source> sourceSet = new HashSet<Source>();
605     for (String source : sourceIds) {
606       if (!StringUtils.isBlank(source)) {
607         sourceSet.add(SourceManager.getInstance().getSource(source));
608       }
609     }
610 
611     return sourceSet;
612   }
613 
614   /**
615    * escape single quotes in javascript
616    * @param string
617    * @return the escaped string
618    */
619   public static String escapeSingleQuotes(String string) {
620     if (string == null) {
621       return string;
622     }
623     return string.replace("'", "\'");
624   }
625   
626   /**
627    * escape double quotes in javascript
628    * @param string
629    * @return the escaped string
630    */
631   public static String escapeDoubleQuotes(String string) {
632     if (string == null) {
633       return string;
634     }
635     return string.replace("\"", "&quot;");
636   }
637   
638   /**
639    * escape double quotes in javascript
640    * @param string
641    * @return the escaped string
642    */
643   public static String escapeDoubleQuotesForString(String string) {
644     if (string == null) {
645       return string;
646     }
647     return string.replace("\"", "\\\"");
648   }
649 
650   /**
651    * escape double quotes in javascript
652    * @param string
653    * @return the escaped string
654    */
655   public static String escapeDoubleQuotesSlashesAndNewlinesForString(String string) {
656     //do slashes first... so they dont get done twice
657     string = escapeSlashesForString(string);
658     string = escapeNewlinesForString(string);
659     string = escapeDoubleQuotesForString(string);
660     return string;
661     
662 //    if (string == null) {
663 //      return string;
664 //    }
665 //    StringBuilder result = new StringBuilder();
666 //    char[] stringCharArray = string.toCharArray();
667 //    for (int i=0; i<string.length(); i++) {
668 //      char curChar = stringCharArray[i];
669 //      if (curChar == '\\') {
670 //        result.append("\\\\");
671 //      } else if (curChar == '"') {
672 //        result.append("\\\"");
673 //      } else if (curChar == '\n') {
674 //        result.append("\\n");
675 //      }
676 //    }
677 //    return string.replace("\n", "\\n");
678 //    return string;
679   }
680 
681   /**
682    * escape double quotes in javascript
683    * @param string
684    * @return the escaped string
685    */
686   public static String escapeNewlinesForString(String string) {
687     if (string == null) {
688       return string;
689     }
690     return string.replace("\n", "\\n");
691   }
692   
693   /**
694    * escape slashes
695    * @param string
696    * @return the escaped string
697    */
698   public static String escapeSlashesForString(String string) {
699     if (string == null) {
700       return string;
701     }
702     return string.replace("\\", "\\\\");
703   }
704   
705   /**
706    * e.g. ('g:gsa', 'jdbc')
707    * @param sources
708    * @return the in string, of sources sorted alphabetically
709    * @deprecated moved to @See HibUtils
710    */
711   @Deprecated
712   public static String convertSourcesToSqlInString(Set<Source> sources) {
713     return HibUtils.convertSourcesToSqlInString(sources);
714   }
715 
716   /**
717    * turn some strings into a map
718    * @param strings
719    * @return the map (never null)
720    */
721   public static Map<String, String> toMap(String... strings) {
722     Map<String, String> map = new LinkedHashMap<String, String>();
723     if (strings != null) {
724       if (strings.length % 2 != 0) {
725         throw new RuntimeException("Must pass in an even number of strings: " + strings.length);
726       }
727       for (int i=0;i<strings.length;i+=2) {
728         map.put(strings[i], strings[i+1]);
729       }
730     }
731     return map;
732   }
733 
734   /**
735    * turn some strings into a map
736    * @param stringObjects is an array of String,Object,String,Object etc where the
737    * Strings are the key, and the Object is the value
738    * @return the map (never null)
739    */
740   public static Map<String, Object> toStringObjectMap(Object... stringObjects) {
741     Map<String, Object> map = new LinkedHashMap<String, Object>();
742     if (stringObjects != null) {
743       if (stringObjects.length % 2 != 0) {
744         throw new RuntimeException("Must pass in an even number of strings: " + stringObjects.length);
745       }
746       for (int i=0;i<stringObjects.length;i+=2) {
747         String key = (String)stringObjects[i];
748         map.put(key, stringObjects[i+1]);
749       }
750     }
751     return map;
752   }
753 
754   /**
755    * convert millis to friendly string
756    * @param duration
757    * @return the friendly string
758    */
759   public static String convertMillisToFriendlyString(Integer duration) {
760     if (duration == null) {
761       return convertMillisToFriendlyString((Long)null);
762     }
763     return convertMillisToFriendlyString(new Long(duration.intValue()));
764   }
765 
766   /**
767    * convert millis to friendly string
768    * @param duration
769    * @return the friendly string
770    */
771   public static String convertMillisToFriendlyString(Long duration) {
772 
773     if (duration == null) {
774       return "";
775     }
776 
777     if (duration < 1000) {
778       return duration + "ms";
779     }
780 
781     long ms = duration % 1000;
782     duration = duration / 1000;
783     long s = duration % 60;
784     duration = duration / 60;
785 
786     if (duration == 0) {
787       return s + "s, " + ms + "ms";
788     }
789 
790     long m = duration % 60;
791     duration = duration / 60;
792 
793     if (duration == 0) {
794       return m + "m, " + s + "s, " + ms + "ms";
795     }
796 
797     long h = duration % 24;
798     duration = duration / 24;
799 
800     if (duration == 0) {
801       return h + "h, " + m + "m, " + s + "s, " + ms + "ms";
802     }
803 
804     long d = duration;
805 
806     return d + "d, " + h + "h, " + m + "m, " + s + "s, " + ms + "ms";
807   }
808 
809   /**
810    * Delete a file, throw exception if cannot
811    * @param file
812    */
813   public static void deleteFile(File file) {
814     //delete and create
815     if (file.exists()) {
816       if (!file.delete()) {
817         throw new RuntimeException("Couldnt delete file: " + file.toString());
818       }
819     }
820   }
821 
822 
823   /**
824    * return the arg after the argBefore, or null if not there, or exception
825    * if argBefore is not found
826    * @param args
827    * @param argBefore
828    * @return the arg
829    */
830   public static String argAfter(String[] args, String argBefore) {
831     if (length(args) <= 1) {
832       return null;
833     }
834     int argBeforeIndex = -1;
835     for (int i=0;i<args.length;i++) {
836       if (equals(args[i], argBefore)) {
837         argBeforeIndex = i;
838         break;
839       }
840     }
841     if (argBeforeIndex == -1) {
842       throw new RuntimeException("Cant find arg before");
843     }
844     if (argBeforeIndex < args.length - 1) {
845       return args[argBeforeIndex + 1];
846     }
847     return null;
848   }
849 
850   /**
851    * append and maybe put a separator in there
852    * @param result
853    * @param separatorIfResultNotEmpty
854    * @param stringToAppend
855    */
856   public static void append(StringBuilder result,
857       String separatorIfResultNotEmpty, String stringToAppend) {
858     if (result.length() != 0) {
859       result.append(separatorIfResultNotEmpty);
860     }
861     result.append(stringToAppend);
862   }
863 
864   /**
865    *
866    */
867   public static final String LOG_ERROR = "Error trying to make parent dirs for logger or logging first statement, check to make " +
868                 "sure you have proper file permissions, and that your servlet container is giving " +
869                 "your app rights to access the log directory (e.g. for tomcat set TOMCAT5_SECURITY=no), g" +
870                 "oogle it for more info";
871 
872   /**
873    * The number of bytes in a kilobyte.
874    */
875   public static final long ONE_KB = 1024;
876 
877   /**
878    * The number of bytes in a megabyte.
879    */
880   public static final long ONE_MB = ONE_KB * ONE_KB;
881 
882   /**
883    * The number of bytes in a gigabyte.
884    */
885   public static final long ONE_GB = ONE_KB * ONE_MB;
886 
887   /**
888    * Grouper home dir
889    */
890   static String grouperHome;
891 
892   static {
893 
894     String theGrouperHome = System.getProperty("grouper.home");
895     if (isBlank(theGrouperHome)) {
896       grouperHome = new File("").getAbsolutePath();
897     } else {
898       grouperHome = theGrouperHome;
899     }
900   }
901 
902 
903   /**
904    * Returns a human-readable version of the file size (original is in
905    * bytes).
906    *
907    * @param size The number of bytes.
908    * @return     A human-readable display value (includes units).
909    * @todo need for I18N?
910    */
911   public static String byteCountToDisplaySize(long size) {
912     String displaySize;
913 
914     if (size / ONE_GB > 0) {
915       displaySize = String.valueOf(size / ONE_GB) + " GB";
916     } else if (size / ONE_MB > 0) {
917       displaySize = String.valueOf(size / ONE_MB) + " MB";
918     } else if (size / ONE_KB > 0) {
919       displaySize = String.valueOf(size / ONE_KB) + " KB";
920     } else {
921       displaySize = String.valueOf(size) + " bytes";
922     }
923 
924     return displaySize;
925   }
926   /**
927    * get a logger, and auto-create log dirs if havent done yet
928    * @param theClass
929    * @return the logger
930    */
931   public static Log getLog(Class<?> theClass) {
932     logDirsCreateIfNotDone();
933     return LogFactory.getLog(theClass);
934   }
935 
936   /**
937    * see if created log dirs
938    */
939   private static boolean logDirsCreated = false;
940 
941 
942   public static void fileCopy(File src, File dest) {
943     try {
944       FileUtils.copyFile(src, dest);
945     } catch (IOException ioe) {
946       throw new RuntimeException("Problem copying: " + (src == null ? null : src.getAbsolutePath()) + " to: " + (dest == null ? null : dest.getAbsolutePath()));
947     }
948   }
949   
950   /**
951    * 
952    * @param exampleResource
953    * @param resource
954    */
955   public static void fileCopyExampleResourceIfNotExist(String exampleResource, String resource) {
956     try {
957       // this could be in a jar
958       if (GrouperUtil.computeUrl(resource, true) != null) {
959         return;
960       }
961       File fileResource = GrouperUtil.fileFromResourceName(resource);
962       if (fileResource == null || !fileResource.exists()) {
963         throw new RuntimeException("File doesnt exist: " + resource);
964       }
965       // works, we good
966     } catch (RuntimeException re) {
967       try {
968         // copy example?
969         File exampleFile = GrouperUtil.fileFromResourceName(exampleResource);
970         
971         fileCopy(exampleFile, new File(exampleFile.getParentFile() + File.separator + resource));
972       } catch (RuntimeException re2) {
973         //ignore and rethrow
974         throw re;
975       }
976     }
977 
978   }
979   
980   /**
981    * auto-create log dirs if not done yet
982    */
983   public static void logDirsCreateIfNotDone() {
984     if (logDirsCreated) {
985       return;
986     }
987     logDirsCreated = true;
988 
989     String location = "log4j.properties";
990     
991     fileCopyExampleResourceIfNotExist("log4j.example.properties", location);
992     Properties properties = propertiesFromResourceName(location);
993     Set<String> keySet = (Set<String>)(Object)properties.keySet();
994     for (String key : keySet) {
995       //if its a file property
996       if (key.endsWith(".File")) {
997         try {
998           String fileName = properties.getProperty(key);
999           if(fileName.startsWith("${grouper.home}")) {
1000             if(grouperHome==null) {
1001             throw new IllegalStateException("The System property grouper.home is referenced in log4j configuration " +
1002                 "however, it is not set.");
1003             }
1004             if (!grouperHome.endsWith("/") && !grouperHome.endsWith("\\")) {
1005               fileName = grouperHome + File.separator + fileName.substring(15);
1006             } else {
1007               fileName = grouperHome + fileName.substring(15);
1008             }
1009           }
1010           File file = new File(fileName);
1011           File parent = file.getParentFile();
1012 
1013           if (parent != null && !parent.exists()) {
1014             //dont have a logger yet, so just print to stdout
1015             System.out.println("Grouper warning: parent dir of log file doesnt exist: " + fileCanonicalPath(parent));
1016             //create the parent
1017             mkdirs(parent);
1018             System.out.println("Grouper note: auto-created parent dir of log file: " + fileCanonicalPath(parent));
1019 
1020           }
1021 
1022         } catch (RuntimeException re) {
1023           //this is bad, print to stderr rightaway (though might dupe)
1024           System.err.println(LOG_ERROR);
1025           re.printStackTrace();
1026           throw new RuntimeException(LOG_ERROR, re);
1027         }
1028       }
1029     }
1030   }
1031 
1032   /**
1033    * find a next exception in the stack and log it
1034    * @param log logger
1035    * @param throwable exception to look for next exceptions in
1036    * @param timeToLive so we dont loop forever
1037    */
1038   public static void logErrorNextException(Log log, Throwable throwable, int timeToLive) {
1039     if (throwable == null) {
1040       return;
1041     }
1042     if (timeToLive < 0) {
1043       throw new RuntimeException("TimeToLive less than 0", throwable);
1044     }
1045     //this is only applicable to sql exceptions
1046     if (throwable instanceof SQLException) {
1047       SQLException sqlException = (SQLException)throwable;
1048       SQLException nextException = sqlException.getNextException();
1049       if (nextException != null) {
1050         if (isInRetriableCode()) {
1051           log.info("Next exception (note, this will be retried so it might not be an issue)", nextException);
1052         } else {
1053           log.error("Next exception", nextException);
1054         }
1055         //maybe there are nested next exceptions....
1056         logErrorNextException(log, nextException, timeToLive-1);
1057       }
1058     }
1059     //recurse to find the next exception
1060     Throwable cause = throwable.getCause();
1061     if (cause != null) {
1062       logErrorNextException(log, cause, timeToLive-1);
1063     }
1064 
1065   }
1066 
1067   /**
1068    * see if options have a specific option by int bits
1069    * @param options
1070    * @param option
1071    * @return if the option is there
1072    */
1073   public static boolean hasOption(int options, int option) {
1074     return (options & option) > 0;
1075   }
1076 
1077   /** keep a cache of db change whitelists */
1078   private static Set<MultiKey> dbChangeWhitelist = new HashSet<MultiKey>();
1079 
1080   /**
1081    * store if we are writing default logs to console
1082    */
1083   private static Boolean printGrouperLogsToConsole = null;
1084 
1085   /**
1086    * if the grouper logs go to the console or not
1087    * @return if
1088    */
1089   public static boolean isPrintGrouperLogsToConsole() {
1090     if (printGrouperLogsToConsole == null) {
1091       logDirPrint();
1092     }
1093     return printGrouperLogsToConsole;
1094   }
1095 
1096   /**
1097    * get canonical path of file
1098    * @param file
1099    * @return the path
1100    */
1101   public static String fileCanonicalPath(File file) {
1102     try {
1103       return file.getCanonicalPath();
1104     } catch (IOException ioe) {
1105       throw new RuntimeException("Problem with file: " + file.getAbsolutePath(), ioe);
1106     }
1107   }
1108 
1109   /**
1110    * log dir message
1111    */
1112   private static String logDirMessage = null;
1113 
1114   /**
1115    * print the log dir to the console so the logs are easy to find
1116    * @return the log dir message
1117    */
1118   public static String logDirPrint() {
1119     logDirsCreateIfNotDone();
1120     //only do this once
1121     if (printGrouperLogsToConsole != null) {
1122       return logDirMessage;
1123     }
1124     StringBuilder resultMessage = new StringBuilder();
1125     printGrouperLogsToConsole = false;
1126     Log rootLogger = LogFactory.getLog("edu.internet2.middleware.grouper");
1127     StringBuilder rootLoggerAppender = new StringBuilder();
1128     boolean writesLogs = false;
1129 
1130 
1131     if (rootLogger instanceof Log4JLogger) {
1132       Category log4jLogger = ((Log4JLogger)rootLogger).getLogger();
1133       int timeToLive = 30;
1134       //if level is null, then go to next.  well, honestly, I dont know
1135       //how the exact algorithm works... :)
1136       while (log4jLogger.getLevel() == null) {
1137         Category parent = log4jLogger.getParent();
1138         if (parent == null) {
1139           break;
1140         }
1141         log4jLogger = parent;
1142         if (timeToLive-- < 0) {
1143           break;
1144         }
1145       }
1146       //add all appenders from here and parents
1147       Set<Appender> allAppenders = new LinkedHashSet<Appender>();
1148       Category currentAppenderLogger = log4jLogger;
1149       while (currentAppenderLogger != null) {
1150         Enumeration allAppendersEnumeration = currentAppenderLogger.getAllAppenders();
1151         while (allAppendersEnumeration.hasMoreElements()) {
1152           allAppenders.add((Appender)allAppendersEnumeration.nextElement());
1153         }
1154         currentAppenderLogger = currentAppenderLogger.getParent();
1155       }
1156 
1157       for (Appender appender : allAppenders) {
1158         writesLogs = true;
1159         if (appender instanceof ConsoleAppender) {
1160           printGrouperLogsToConsole = true;
1161           rootLoggerAppender.append("console, ");
1162         } else if (appender instanceof FileAppender) {
1163           String path = ((FileAppender)appender).getFile();
1164           if (isBlank(path)) {
1165             resultMessage.append("Grouper error, file appender path is empty, maybe dir doesnt exist\n");
1166           } else {
1167             File logFile = new File(path);
1168             if (logFile.getParentFile() != null && !logFile.getParentFile().exists()) {
1169               resultMessage.append("Grouper warning: parent dir of log file doesnt exist: " + logFile.getAbsolutePath() + "\n");
1170               mkdirs(logFile.getParentFile());
1171               resultMessage.append("Grouper note: auto-created parent dir of log file: " + logFile.getAbsolutePath() + "\n");
1172             }
1173             rootLoggerAppender.append(logFile.getAbsolutePath()).append(", ");
1174           }
1175         } else {
1176           rootLoggerAppender.append("appender type: " + appender.getClass().getSimpleName()).append(", ");
1177         }
1178       }
1179       if (!writesLogs || !rootLogger.isErrorEnabled()) {
1180         resultMessage.append("Grouper warning, it is detected that you are not logging errors for " +
1181             "package edu.internet2.middleware.grouper, you should enable logging at " +
1182             "least at the WARN level in log4j.properties\n");
1183       } else {
1184         if (rootLogger.isErrorEnabled() && !rootLogger.isWarnEnabled()) {
1185           resultMessage.append("Grouper warning, it is detected that you are logging " +
1186               "edu.internet2.middleware.grouper as ERROR and not WARN level.  It is " +
1187               "recommended to log at at least WARN level in log4j.properties\n");
1188         }
1189         String logLevel = null;
1190         if (rootLogger.isTraceEnabled()) {
1191           logLevel = "TRACE";
1192         } else if (rootLogger.isDebugEnabled()) {
1193           logLevel = "DEBUG";
1194         } else if (rootLogger.isInfoEnabled()) {
1195           logLevel = "INFO";
1196         } else if (rootLogger.isWarnEnabled()) {
1197           logLevel = "WARN";
1198         } else if (rootLogger.isErrorEnabled()) {
1199           logLevel = "ERROR";
1200         } else if (rootLogger.isFatalEnabled()) {
1201           logLevel = "FATAL";
1202         }
1203         resultMessage.append("Grouper is logging to file:   " + rootLoggerAppender + "at min level "
1204             + logLevel + " for package: edu.internet2.middleware.grouper, based on log4j.properties\n");
1205       }
1206     } else {
1207       resultMessage.append("Grouper logs are not using log4j: " + (rootLogger == null ? null : rootLogger.getClass()) + "\n");
1208     }
1209     logDirMessage = resultMessage.toString();
1210     return logDirMessage;
1211   }
1212 
1213   /**
1214    * return the suffix after a char.  If the char doesnt exist, just return the string
1215    * @param input string
1216    * @param theChar char
1217    * @return new string
1218    */
1219   public static String suffixAfterChar(String input, char theChar) {
1220     if (input == null) {
1221       return null;
1222     }
1223     //get the real type off the end
1224     int lastIndex = input.lastIndexOf(theChar);
1225     if (lastIndex > -1) {
1226       input = input.substring(lastIndex + 1, input.length());
1227     }
1228     return input;
1229   }
1230 
1231   /**
1232    * get the oracle underscore name e.g. javaNameHere -> JAVA_NAME_HERE
1233    *
1234    * @param javaName
1235    *          the java convention name
1236    *
1237    * @return the oracle underscore name based on the java name
1238    */
1239   public static String oracleStandardNameFromJava(String javaName) {
1240 
1241     StringBuilder result = new StringBuilder();
1242 
1243     if ((javaName == null) || (0 == "".compareTo(javaName))) {
1244       return javaName;
1245     }
1246 
1247     //if package is specified, only look at class name
1248     javaName = suffixAfterChar(javaName, '.');
1249 
1250     //dont check the first char
1251     result.append(javaName.charAt(0));
1252 
1253     char currChar;
1254 
1255     boolean previousCap = false;
1256 
1257     //loop through the string, looking for uppercase
1258     for (int i = 1; i < javaName.length(); i++) {
1259       currChar = javaName.charAt(i);
1260 
1261       //if uppcase append an underscore
1262       if (!previousCap && (currChar >= 'A') && (currChar <= 'Z')) {
1263         result.append("_");
1264       }
1265 
1266       result.append(currChar);
1267       if ((currChar >= 'A') && (currChar <= 'Z')) {
1268         previousCap = true;
1269       } else {
1270         previousCap = false;
1271       }
1272     }
1273 
1274     //this is in upper-case
1275     return result.toString().toUpperCase();
1276   }
1277 
1278 
1279   /**
1280    * see if two maps are the equivalent (based on number of entries,
1281    * and the equals() method of the keys and values)
1282    * @param <K>
1283    * @param <V>
1284    * @param first
1285    * @param second
1286    * @return true if equal
1287    */
1288   public static <K,V> boolean mapEquals(Map<K,V> first, Map<K,V> second) {
1289     Set<K> keysMismatch = new HashSet<K>();
1290     mapDifferences(first, second, keysMismatch, null);
1291     //if any keys mismatch, then not equal
1292     return keysMismatch.size() == 0;
1293 
1294   }
1295 
1296   /**
1297    * empty map
1298    */
1299   private static final Map EMPTY_MAP = Collections.unmodifiableMap(new HashMap());
1300 
1301   /**
1302    * see if two maps are the equivalent (based on number of entries,
1303    * and the equals() method of the keys and values)
1304    * @param <K>
1305    * @param <V>
1306    * @param first map to check diffs
1307    * @param second map to check diffs
1308    * @param differences set of keys (with prefix) of the diffs
1309    * @param prefix for the entries in the diffs (e.g. "attribute__"
1310    */
1311   public static <K,V> void mapDifferences(Map<K,V> first, Map<K,V> second, Set<K> differences, String prefix) {
1312     if (first == second) {
1313       return;
1314     }
1315     //put the collections in new collections so we can remove and keep track
1316     if (first == null) {
1317       first = EMPTY_MAP;
1318     }
1319     if (second == null) {
1320       second = EMPTY_MAP;
1321     } else {
1322       //make linked so the results are ordered
1323       second = new LinkedHashMap<K,V>(second);
1324     }
1325     int firstSize = first == null ? 0 : first.size();
1326     int secondSize = second == null ? 0 : second.size();
1327     //if both empty then all good
1328     if (firstSize == 0 && secondSize == 0) {
1329       return;
1330     }
1331 
1332     for (K key : first.keySet()) {
1333 
1334       if (second.containsKey(key)) {
1335         V firstValue = first.get(key);
1336         V secondValue = second.get(key);
1337         //keep track by removing from second
1338         second.remove(key);
1339         if (equals(firstValue, secondValue)) {
1340           continue;
1341         }
1342       }
1343       differences.add(isNotBlank(prefix) ? (K)(prefix + key) : key);
1344     }
1345     //add the ones left over in the second map which are not in the first map
1346     for (K key : second.keySet()) {
1347       differences.add(isNotBlank(prefix) ? (K)(prefix + key) : key);
1348     }
1349   }
1350 
1351   /**
1352    * sleep, if interrupted, throw runtime
1353    * @param millis
1354    */
1355   public static void sleep(long millis) {
1356     if (millis == 0) {
1357       return;
1358     }
1359     try {
1360       Thread.sleep(millis);
1361     } catch (InterruptedException ie) {
1362       throw new RuntimeException(ie);
1363     }
1364   }
1365 
1366   /**
1367    *
1368    * @param seconds
1369    */
1370   public static void sleepWithStdoutCountdown(int seconds) {
1371     for (int i=seconds;i>0;i--) {
1372       System.out.println("Sleeping: " + i);
1373       sleep(1000);
1374     }
1375   }
1376 
1377   /**
1378    * encrypt a message to SHA
1379    * @param plaintext
1380    * @return the hash
1381    */
1382   public synchronized static String encryptSha(String plaintext) {
1383     MessageDigest md = null;
1384     try {
1385       md = MessageDigest.getInstance("SHA"); //step 2
1386     } catch (NoSuchAlgorithmException e) {
1387       throw new RuntimeException(e);
1388     }
1389     try {
1390       md.update(plaintext.getBytes("UTF-8")); //step 3
1391     } catch (UnsupportedEncodingException e) {
1392       throw new RuntimeException(e);
1393   }
1394     byte raw[] = md.digest(); //step 4
1395     byte[] encoded = Base64.encodeBase64(raw); //step 5
1396     String hash = new String(encoded);
1397     //String hash = (new BASE64Encoder()).encode(raw); //step 5
1398     return hash; //step 6
1399   }
1400 
1401   /**
1402    * If we can, inject this into the exception, else return false
1403    * @param t
1404    * @param message
1405    * @return true if success, false if not
1406    */
1407   public static boolean injectInException(Throwable t, String message) {
1408 
1409     String throwableFieldName = GrouperConfig.retrieveConfig().propertyValueString("throwable.data.field.name");
1410 
1411     if (isBlank(throwableFieldName)) {
1412       //this is the field for sun java 1.5
1413       throwableFieldName = "detailMessage";
1414     }
1415     try {
1416       String currentValue = t.getMessage();
1417       if (!isBlank(currentValue)) {
1418         currentValue += ",\n" + message;
1419       } else {
1420         currentValue = message;
1421       }
1422       assignField(t, throwableFieldName, currentValue);
1423       return true;
1424     } catch (Throwable t2) {
1425       //dont worry about what the problem is, return false so the caller can log
1426       return false;
1427     }
1428 
1429   }
1430 
1431   /**
1432    * see if there is a grouper properties db match
1433    * @param whitelist true if whitelist, false if blacklist
1434    * @param user is the db user
1435    * @param url is the db url
1436    * @return true if found match, false if not
1437    */
1438   public static boolean findGrouperPropertiesDbMatch(boolean whitelist, String user, String url) {
1439 
1440     //lets check the whitelist and blacklist first
1441     Properties grouperProperties = GrouperConfig.retrieveConfig().properties();
1442 
1443     //check blacklist
1444     //db.change.deny.user.0=
1445     //db.change.deny.url.0=
1446     //db.change.allow.user.0=grouper
1447     //db.change.allow.url.0=jdbc:mysql://localhost:3306/grouper
1448 
1449     int index = 0;
1450     String typeString = whitelist ? "allow" : "deny";
1451     while (true) {
1452       String currentUser = trim(grouperProperties.getProperty(
1453           "db.change." + typeString + ".user." + index));
1454       String currentUrl = trim(grouperProperties.getProperty(
1455           "db.change." + typeString + ".url." + index));
1456 
1457       //if we are done checking
1458       if (isBlank(currentUser) || isBlank(currentUrl)) {
1459         break;
1460       }
1461       if (equals(currentUser, user) && equals(currentUrl, url)) {
1462         return true;
1463       }
1464       index++;
1465     }
1466     return false;
1467   }
1468 
1469   /** prompt key for schema export */
1470   public static String PROMPT_KEY_SCHEMA_EXPORT_ALL_TABLES = "schemaexport all tables";
1471 
1472   /** prompt key for reset data */
1473   public static String PROMPT_KEY_RESET_DATA = "delete all grouper data";
1474 
1475   /** dont prompt while testing etc, but make sure there has been at least one prompt */
1476   public static boolean stopPromptingUser = false;
1477 
1478   /** if string is not in here, echo to screen */
1479   private static Set<String> stopPromptingUserPrintlns = new HashSet<String>();
1480 
1481   /**
1482    * prompt the user about db changes
1483    * @param reason e.g. delete all tables
1484    * @param checkResponse true if the response from the user should be checked, or just display the prompt
1485    */
1486   public static void promptUserAboutDbChanges(String reason, boolean checkResponse) {
1487 
1488     Properties grouperHibernateProperties = GrouperHibernateConfig.retrieveConfig().properties();
1489 
1490     String url = trim(grouperHibernateProperties.getProperty("hibernate.connection.url"));
1491     String user = trim(grouperHibernateProperties.getProperty("hibernate.connection.username"));
1492 
1493     promptUserAboutChanges(reason, checkResponse, "db", url, user);
1494   }
1495 
1496   /**
1497    * convert from uid=someapp,ou=people,dc=myschool,dc=edu
1498    * baseDn is edu
1499    * searchDn is myschool
1500    * to people:someapp
1501    * i.e. take apart a bushy dns
1502    * @param dn
1503    * @param baseDn if there is one, take it off
1504    * @param searchDn if there is one after the baseDn is off, take it off
1505    * @return the subpath
1506    */
1507   public static String ldapConvertDnToSubPath(String dn, String baseDn, String searchDn) {
1508 
1509     // not sure why this would happen...
1510     if (StringUtils.isBlank(dn)) {
1511       return dn;
1512     }
1513 
1514     if (!StringUtils.isBlank(baseDn)) {
1515       if (dn.endsWith(baseDn)) {
1516         dn = dn.substring(0, dn.length() - (baseDn.length()+1));
1517       }
1518     }
1519     if (!StringUtils.isBlank(searchDn)) {
1520       if (dn.endsWith(searchDn)) {
1521         dn = dn.substring(0, dn.length() - (searchDn.length()+1));
1522       }
1523     }
1524     // not sure why this would happen...
1525     if (StringUtils.isBlank(dn)) {
1526       return dn;
1527     }
1528 
1529     DN theDn = null;
1530     try {
1531       theDn = new DN(dn);
1532     } catch (LDAPException ldapException) {
1533       throw new RuntimeException("Cant parse DN: '" + dn + "'", ldapException);
1534     }
1535     
1536     RDN[] rdns = theDn.getRDNs();
1537     StringBuilder path = new StringBuilder();
1538     for (int i=rdns.length-1;i>=0;i--) {
1539       RDN rdn = rdns[i];
1540       path.append(rdn.getAttributeValues()[0]);
1541       if (i != 0) {
1542         path.append(":");
1543       }
1544       
1545     }
1546     return path.toString();
1547   }
1548 
1549   /**
1550    * convert from uid=someapp,ou=people,dc=myschool,dc=edu
1551    * to someapp
1552    * @param dn
1553    * @return
1554    */
1555   public static String ldapConvertDnToSpecificValue(String dn) {
1556 
1557     // not sure why this would happen...
1558     if (StringUtils.isBlank(dn)) {
1559       return dn;
1560     }
1561     try {
1562       DN theDn = new DN(dn);
1563       RDN[] rdns = theDn.getRDNs();
1564       RDN firstRdn = rdns[0];
1565       return firstRdn.getAttributeValues()[0];
1566     } catch (LDAPException ldapException) {
1567       throw new RuntimeException("Cant parse DN: '" + dn + "'", ldapException);
1568     }
1569   }
1570 
1571   /**
1572    * This takes a string of attribute=value and makes sure that special, dn-relevant characters
1573    * are escaped, particularly commas, pluses, etc
1574    * @param rdnString An RDN: attribute=value
1575    * @return
1576    */
1577   public static String ldapEscapeRdn(String rdnString) {
1578 
1579     String rdnAttribute = StringUtils.substringBefore(rdnString, "=");
1580     String rdnValue     = StringUtils.substringAfter(rdnString, "=");
1581 
1582     if ( StringUtils.isEmpty(rdnValue) || StringUtils.isEmpty(rdnValue) ) {
1583       throw new RuntimeException("Unable to parse and escape rdn: '" + rdnString + "'");
1584     }
1585 
1586     // This is wrapping the Value in quotes so the RDN class will consider
1587     // all the dn-relevant characters (eg: ,+;) as escaped
1588     RDN rdn = new RDN(rdnAttribute, rdnValue);
1589     return rdn.toMinimallyEncodedString();
1590   }
1591 
1592   /**
1593    * This takes a string of value and makes sure that special, dn-relevant characters
1594    * are escaped, particularly commas, pluses, etc
1595    * @param rdnString An RDN value: value
1596    * @return the escaped value
1597    */
1598   public static String ldapEscapeRdnValue(String rdnValue) {
1599 
1600     // This is wrapping the Value in quotes so the RDN class will consider
1601     // all the dn-relevant characters (eg: ,+;) as escaped
1602     //add a sample prefix, and then strip it off
1603     RDN rdn = new RDN("cn", rdnValue);
1604     return rdn.toMinimallyEncodedString().substring("cn=".length());
1605   }
1606   
1607   /**
1608    * 
1609    * @param groupName
1610    * @param rdnAttributeName
1611    * @param ouAttributeName
1612    * @param performRdnEscaping
1613    * @param performFilterEscaping
1614    * @return
1615    */
1616   public static String ldapBushyDn(String groupName, String rdnAttributeName,
1617       String ouAttributeName,
1618       boolean performRdnEscaping, boolean performFilterEscaping) {
1619     return ldapBushyDn(groupName, rdnAttributeName, null, ouAttributeName, performRdnEscaping, performFilterEscaping);
1620   }
1621 
1622   /**
1623    * 
1624    * @param groupName
1625    * @param rdnAttributeName
1626    * @param rdnAttributeValue
1627    * @param ouAttributeName
1628    * @param performRdnEscaping
1629    * @param performFilterEscaping
1630    * @return
1631    */
1632   public static String ldapBushyDn(String groupName, String rdnAttributeName,
1633       String rdnAttributeValue, String ouAttributeName,
1634       boolean performRdnEscaping, boolean performFilterEscaping) {
1635 
1636     StringBuilder result = new StringBuilder();
1637     
1638     List<String> namePieces=Arrays.asList(groupName.split(":"));
1639     Collections.reverse(namePieces);
1640 
1641     /// Work through the pieces backwards. The first is rdn=X and the others are ou=X
1642     for (int i=0; i<namePieces.size(); i++) {
1643       if ( result.length() != 0 ) {
1644         result.append(',');
1645       }
1646       
1647       RDN rdn;
1648       String piece;
1649       
1650       if (i==0 && rdnAttributeValue != null) {
1651         piece = new String(rdnAttributeValue);
1652       } else {
1653         piece = namePieces.get(i);        
1654       }
1655 
1656       // Look for filter-relevant characters if this will be used in a filter
1657       if ( performFilterEscaping ) {
1658         piece = ldapFilterEscape(piece);
1659       }
1660 
1661       if (i==0) {
1662         rdn = new RDN(rdnAttributeName, piece);
1663       } else {
1664         rdn = new RDN(ouAttributeName, piece);
1665       }
1666 
1667       if ( performRdnEscaping ) {
1668         result.append(rdn.toMinimallyEncodedString());
1669       } else {
1670         result.append(rdn.toString());
1671       }
1672     }
1673 
1674     return result.toString();
1675 
1676   }
1677   
1678   public static String ldapFilterEscape(String s) {
1679     // TODO replace with ldaptive 2.0 FilterUtils.escape after ldaptive upgrade
1680     if (s == null) {
1681       return s;
1682     }
1683     final StringBuilder sb = new StringBuilder(s.length());
1684     final byte[] utf8 = s.getBytes(StandardCharsets.UTF_8);
1685     // CheckStyle:MagicNumber OFF
1686     // optimize if ASCII
1687     if (s.length() == utf8.length) {
1688       for (byte b : utf8) {
1689         if (b <= 0x1F || b == 0x28 || b == 0x29 || b == 0x2A || b == 0x5C || b == 0x7F) {
1690           sb.append('\\').append(Hex.encode(new byte[] {b}));
1691         } else {
1692           sb.append((char) b);
1693         }
1694       }
1695     } else {
1696       int multiByte = 0;
1697       for (byte b : utf8) {
1698         if (multiByte > 0) {
1699           sb.append('\\').append(Hex.encode(new byte[] {b}));
1700           multiByte--;
1701         } else if ((b & 0x7F) == b) {
1702           if (b <= 0x1F || b == 0x28 || b == 0x29 || b == 0x2A || b == 0x5C || b == 0x7F) {
1703             sb.append('\\').append(Hex.encode(new byte[] {b}));
1704           } else {
1705             sb.append((char) b);
1706           }
1707         } else {
1708           // 2 byte character
1709           if ((b & 0xE0) == 0xC0) {
1710             multiByte = 1;
1711             // 3 byte character
1712           } else if ((b & 0xF0) == 0xE0) {
1713             multiByte = 2;
1714             // 4 byte character
1715           } else if ((b & 0xF8) == 0xF0) {
1716             multiByte = 3;
1717           } else {
1718             throw new IllegalStateException("Could not read UTF-8 string encoding");
1719           }
1720           sb.append('\\').append(Hex.encode(new byte[] {b}));
1721         }
1722       }
1723     }
1724     // CheckStyle:MagicNumber ON
1725     return sb.toString();
1726   }
1727   
1728   /**
1729    * prompt the user about db changes
1730    * @param reason e.g. delete all tables
1731    * @param checkResponse true if the response from the user should be checked, or just display the prompt
1732    * @param dbType should be db or ldap
1733    * @param url to check for
1734    * @param user user for db
1735    */
1736   public static void promptUserAboutChanges(String reason, boolean checkResponse, String dbType, String url, String user) {
1737 
1738     MultiKey cacheKey = stopPromptingUser ? new MultiKey(url, user) : new MultiKey(reason, url, user);
1739 
1740     //if already ok'ed this question in the jre instance, then we are all set
1741     if (dbChangeWhitelist.contains(cacheKey)) {
1742       //maybe stop due to testing and at least one
1743       if (stopPromptingUser) {
1744         String message = dbType + " prompting has been disabled (e.g. due to testing), so this user '"
1745             + user + "' and url '" + url + "' are allowed for: " + reason;
1746         if (!stopPromptingUserPrintlns.contains(message)) {
1747           System.out.println(message);
1748         }
1749         stopPromptingUserPrintlns.add(message);
1750         return;
1751       }
1752       return;
1753     }
1754 
1755     //this might be set from junit ant task
1756     String allow = System.getProperty("grouper.allow.db.changes");
1757     if (equals("true", allow)) {
1758       System.out.println("System property grouper.allow.db.changes is true which allows " + dbType + " changes to user '"
1759           + user + "' and url '" + url + "'");
1760       //all good, add to cache so we dont have to repeatedly tell user
1761       dbChangeWhitelist.add(cacheKey);
1762       return;
1763     }
1764 
1765     //check blacklist
1766     if (findGrouperPropertiesDbMatch(false, user, url)) {
1767       System.out.println("This " + dbType + " user '" + user + "' and url '" + url + "' are denied to be " +
1768           "changed in the grouper.properties");
1769       System.exit(1);
1770 
1771     }
1772 
1773     //check whitelist
1774     if (findGrouperPropertiesDbMatch(true, user, url)) {
1775       System.out.println("This " + dbType + " user '" + user + "' and url '" + url + "' are allowed to be " +
1776           "changed in the grouper.properties");
1777       if (!checkResponse) {
1778         System.out.println("Unfortunately this is checked from ant so you have to type 'y' anyways...");
1779       }
1780     } else {
1781 
1782       BufferedReader stdin = null;
1783       String message = null; // Creates a variable called message for input
1784 
1785       try {
1786         stdin = new BufferedReader(new InputStreamReader(System.in));
1787 
1788         //CH 20080506: THIS DOESNT WORK!
1789         //make sure there is nothing already on stdin
1790         //int available = System.in.available();
1791         //System.out.println("Available: " + available);
1792         //
1793         ////read these on stdin
1794         //if (available > 0) {
1795         //  stdin.read(new char[available]);
1796         //}
1797 
1798         //ask user if ok
1799         System.out.println("(note, might need to type in your response multiple times (Java stdin is flaky))");
1800         System.out.println("(note, you can whitelist or blacklist " + dbType + " urls and users in the grouper.properties)");
1801         //note the following must be println and not just print so it will show up in ant
1802         String prompt = "Are you sure you want to " + reason + " in " + dbType + " user '" + user + "', " + dbType + " url '" + url + "'? (y|n): ";
1803         System.out.println(prompt);
1804         System.out.flush(); // empties buffer, before you input text
1805         if (!checkResponse) {
1806           return;
1807         }
1808         //we want to read until we dont get empty, and until we get a y or an n
1809         for (int i=0;i<10;i++) {
1810           message = stdin.readLine();
1811           message = trimToEmpty(message);
1812           if (!isEmpty(message)) {
1813             if (equalsIgnoreCase(message, "y") || equalsIgnoreCase(message, "n")) {
1814               break;
1815             }
1816             System.out.println("Didn't receive 'y' or 'n', received '" + message + "'...");
1817             System.out.println(prompt);
1818             System.out.flush(); // empties buffer, before you input text
1819           }
1820         }
1821         if (!equalsIgnoreCase(message, "y") && !equalsIgnoreCase(message, "n")) {
1822           System.out.println("Sorry you are having trouble, try the whitelist in grouper.properties");
1823           System.exit(1);
1824         }
1825       } catch (Exception e) {
1826         throw new RuntimeException(e);
1827       //CH: 20080506: Maybe we shouldnt close stdin... wont be able to use again?
1828       //} finally {
1829       //  closeQuietly(stdin);
1830       }
1831       if (!equalsIgnoreCase(message, "y")) {
1832         System.out.println("Didn't receive 'y', received '" + message + "', OK, exiting");
1833         System.exit(1);
1834       }
1835     }
1836     //all good, add to cache so we dont have to repeatedly ask user
1837     dbChangeWhitelist.add(cacheKey);
1838     System.out.println("Continuing...");
1839   }
1840 
1841   /**
1842    * get a unique string identifier based on the current time,
1843    * this is not globally unique, just unique for as long as this
1844    * server is running...
1845    *
1846    * @return String
1847    */
1848   public static String uniqueId() {
1849     //this needs to be threadsafe since we are using a static field
1850     synchronized (GrouperUtil.class) {
1851       lastId = incrementStringInt(lastId);
1852     }
1853 
1854     return String.valueOf(lastId);
1855   }
1856 
1857   /**
1858    * get a file name from a resource name
1859    *
1860    * @param resourceName
1861    *          is the classpath location
1862    *
1863    * @return the file path on the system
1864    */
1865   public static File fileFromResourceName(String resourceName) {
1866 
1867     URL url = computeUrl(resourceName, true);
1868 
1869     if (url == null) {
1870       return null;
1871     }
1872     try {
1873       String fileName = URLDecoder.decode(url.getFile(), "UTF-8");
1874 
1875       File configFile = new File(fileName);
1876 
1877     return configFile;
1878     } catch (UnsupportedEncodingException uee) {
1879       throw new RuntimeException(uee);
1880     }
1881   }
1882 
1883   /**
1884    * get a full path from a resource name
1885    *
1886    * @param resourceName
1887    *          is the classpath location
1888    *
1889    * @return Full path to the resource. For files, the file path on the system; otherwise, the resource URL
1890    */
1891   public static String getLocationFromResourceName(String resourceName) {
1892 
1893     URL url = computeUrl(resourceName, true);
1894 
1895     if (url == null) {
1896       return null;
1897     }
1898 
1899     if (url.getProtocol() == "file") {
1900       try {
1901         String urlFile = URLDecoder.decode(url.getFile(), "UTF-8");
1902         return fileCanonicalPath(new File(urlFile));
1903       } catch (UnsupportedEncodingException uee) {
1904         throw new RuntimeException(uee);
1905       }
1906     }
1907 
1908     try {
1909       String urlPath = URLDecoder.decode(url.toString(), "UTF-8");
1910       return urlPath;
1911     } catch (UnsupportedEncodingException uee) {
1912       throw new RuntimeException(uee);
1913     }
1914   }
1915 
1916   /**
1917    * compute a url of a resource
1918    * @param resourceName
1919    * @param canBeNull if cant be null, throw runtime
1920    * @return the URL
1921    */
1922   public static URL computeUrl(String resourceName, boolean canBeNull) {
1923     //get the url of the navigation file
1924     ClassLoader cl = classLoader();
1925 
1926     URL url = null;
1927 
1928     try {
1929       //CH 20081012: sometimes it starts with slash and it shouldnt...
1930       String newResourceName = resourceName.startsWith("/")
1931         ? resourceName.substring(1) : resourceName;
1932       url = cl.getResource(newResourceName);
1933     } catch (NullPointerException npe) {
1934       String error = "computeUrl() Could not find resource file: " + resourceName;
1935       throw new RuntimeException(error, npe);
1936     }
1937 
1938     if (!canBeNull && url == null) {
1939       throw new RuntimeException("Cant find resource: " + resourceName);
1940     }
1941 
1942     return url;
1943   }
1944 
1945 
1946   /**
1947    * fast class loader
1948    * @return the class loader
1949    */
1950   public static ClassLoader classLoader() {
1951     return GrouperUtil.class.getClassLoader();
1952   }
1953 
1954   /**
1955    * make sure a array is non null.  If null, then return an empty array.
1956    * @param <T>
1957    * @param array
1958    * @param theClass to make array from
1959    * @return the list or empty list if null
1960    */
1961   @SuppressWarnings({ "unchecked", "cast" })
1962   public static <T> T[] nonNull(T[] array, Class<?> theClass) {
1963     if (int.class.equals(theClass)) {
1964       return (T[])(Object)new int[0];
1965     }
1966     if (float.class.equals(theClass)) {
1967       return (T[])(Object)new float[0];
1968     }
1969     if (double.class.equals(theClass)) {
1970       return (T[])(Object)new double[0];
1971     }
1972     if (short.class.equals(theClass)) {
1973       return (T[])(Object)new short[0];
1974     }
1975     if (long.class.equals(theClass)) {
1976       return (T[])(Object)new long[0];
1977     }
1978     if (byte.class.equals(theClass)) {
1979       return (T[])(Object)new byte[0];
1980     }
1981     if (boolean.class.equals(theClass)) {
1982       return (T[])(Object)new boolean[0];
1983     }
1984     if (char.class.equals(theClass)) {
1985       return (T[])(Object)new char[0];
1986     }
1987     return array == null ? ((T[])Array.newInstance(theClass, 0)) : array;
1988   }
1989 
1990   /**
1991    * strip the suffix off
1992    * @param string
1993    * @param suffix
1994    * @return the string without the suffix
1995    */
1996   public static String stripSuffix(String string, String suffix) {
1997     if (string == null || suffix == null) {
1998       return string;
1999     }
2000     if (string.endsWith(suffix)) {
2001       return string.substring(0, string.length() - suffix.length());
2002     }
2003     return string;
2004   }
2005 
2006   /**
2007    * strip the prefix off
2008    * @param string
2009    * @param prefix
2010    * @return the string without the suffix
2011    */
2012   public static String stripPrefix(String string, String prefix) {
2013     if (string == null || prefix == null) {
2014       return string;
2015     }
2016     if (string.startsWith(prefix)) {
2017       return string.substring(prefix.length(), string.length());
2018     }
2019     return string;
2020   }
2021 
2022   /**
2023    * get the prefix or suffix of a string based on a separator
2024    *
2025    * @param startString
2026    *          is the string to start with
2027    * @param separator
2028    *          is the separator to split on
2029    * @param isPrefix
2030    *          if thre prefix or suffix should be returned
2031    *
2032    * @return the prefix or suffix, if the separator isnt there, return the
2033    *         original string
2034    */
2035   public static String prefixOrSuffix(String startString, String separator,
2036       boolean isPrefix) {
2037     String prefixOrSuffix = null;
2038 
2039     //no nulls
2040     if (startString == null) {
2041       return startString;
2042     }
2043 
2044     //where is the separator
2045     int separatorIndex = isPrefix ? startString.indexOf(separator) : startString.lastIndexOf(separator);
2046 
2047     //if none exists, dont proceed
2048     if (separatorIndex == -1) {
2049       return startString;
2050     }
2051 
2052     //maybe the separator isnt on character
2053     int separatorLength = separator.length();
2054 
2055     if (isPrefix) {
2056       prefixOrSuffix = startString.substring(0, separatorIndex);
2057     } else {
2058       prefixOrSuffix = startString.substring(separatorIndex + separatorLength,
2059           startString.length());
2060     }
2061 
2062     return prefixOrSuffix;
2063   }
2064 
2065   /**
2066    * <pre>
2067    * this method will indent xml or json.
2068    * this is for logging or documentations purposes only and should
2069    * not be used for a production use (since it is not 100% tested
2070    * or compliant with all constructs like xml CDATA
2071    *
2072    * For xml, assumes elements either have text or sub elements, not both.
2073    * No cdata, nothing fancy.
2074    *
2075    * 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;
2076    * It would output:
2077    * &lt;a&gt;
2078    *   &lt;b&gt;
2079    *     &lt;c&gt;hey&lt;/c&gt;
2080    *     &lt;d&gt;
2081    *       &lt;e&gt;there&lt;/e&gt;
2082    *     &lt;/d&gt;
2083    *   &lt;/b&gt;
2084    * &lt;/a&gt;
2085    *
2086    * For , if the input is: {"a":{"b\"b":{"c\\":"d"},"e":"f","g":["h":"i"]}}
2087    * It would output:
2088    * {
2089    *   "a":{
2090    *     "b\"b":{
2091    *       "c\\":"d"
2092    *     },
2093    *     "e":"f",
2094    *     "g":[
2095    *       "h":"i"
2096    *     ]
2097    *   }
2098    * }
2099    *
2100    *
2101    * <pre>
2102    * @param string
2103    * @param failIfTypeNotFound
2104    * @return the indented string, 2 spaces per line
2105    */
2106   public static String indent(String string, boolean failIfTypeNotFound) {
2107     if (string == null) {
2108       return null;
2109     }
2110     string = trim(string);
2111     if (string.startsWith("<")) {
2112       //this is xml
2113       return new XmlIndenter(string).result();
2114     }
2115     if (string.startsWith("{")) {
2116       return new JsonIndenter(string).result();
2117     }
2118     if (!failIfTypeNotFound) {
2119       //just return if cant indent
2120       return string;
2121     }
2122     throw new RuntimeException("Cant find type of string: " + string);
2123   }
2124 
2125   /**
2126    * convert an object to json.
2127    * @param object
2128    * @return the string of json
2129    */
2130   public static String jsonConvertTo(Object object) {
2131     return jsonConvertTo(object, true);
2132   }
2133 
2134   /**
2135    * convert an object to json.
2136    * @param object
2137    * @return the string of json
2138    */
2139   public static String jsonConvertTo(Object object, boolean includeObjectNameWrapper) {
2140 
2141     if (object == null) {
2142       throw new NullPointerException();
2143     }
2144 //    Gson gson = new GsonBuilder().create();
2145 //    String json = gson.toJson(object);
2146 
2147 //    JSONObject jsonObject = net.sf.json.JSONObject.fromObject( object );
2148 //    String json = jsonObject.toString();
2149 
2150     JsonConfig jsonConfig = new JsonConfig();
2151     jsonConfig.setJsonPropertyFilter( new PropertyFilter(){
2152        public boolean apply( Object source, String name, Object value ) {
2153          //json-lib cannot handle maps where the key is not a string
2154          if( value != null && value instanceof Map ){
2155            Map map = (Map)value;
2156            if (map.size() > 0 && !(map.keySet().iterator().next() instanceof String)) {
2157              return true;
2158            }
2159          }
2160          return value == null;
2161        }
2162     });
2163     JSONObject jsonObject = JSONObject.fromObject( object, jsonConfig );
2164     String json = jsonObject.toString();
2165 
2166     if (!includeObjectNameWrapper) {
2167       return json;
2168     }
2169     return "{\"" + object.getClass().getSimpleName() + "\":" + json + "}";
2170   }
2171 
2172 
2173   /**
2174    * get a field as string and handle null
2175    * @param jsonNode
2176    * @param fieldName
2177    * @return the string
2178    */
2179   public static String jsonJacksonGetString(JsonNode jsonNode, String fieldName) {
2180     return jsonJacksonGetString(jsonNode, fieldName, null);
2181   }
2182   
2183   /**
2184    * assign a jackson field
2185    * @param jsonNode
2186    * @param fieldName
2187    * @return the string
2188    */
2189   public static void jsonJacksonAssignString(ObjectNode objectNode, String fieldName, String value) {
2190     if (!StringUtils.isBlank(value)) {
2191       objectNode.put(fieldName, value);
2192     }
2193   }
2194   
2195   /**
2196    * assign a jackson field
2197    * @param jsonNode
2198    * @param fieldName
2199    * @return the string
2200    */
2201   public static void jsonJacksonAssignBoolean(ObjectNode objectNode, String fieldName, Boolean value) {
2202     if (value != null) {
2203       objectNode.put(fieldName, value);
2204     }
2205   }
2206   
2207   /**
2208    * assign a jackson field
2209    * @param jsonNode
2210    * @param fieldName
2211    */
2212   public static void jsonJacksonAssignLong(ObjectNode objectNode, String fieldName, Long value) {
2213     if (value != null) {
2214       objectNode.put(fieldName, value);
2215     }
2216   }
2217   
2218   /**
2219    * assign a jackson field
2220    * @param jsonNode
2221    * @param fieldName
2222    * @return the string
2223    */
2224   public static void jsonJacksonAssignStringArray(ObjectNode objectNode, String fieldName, Collection<String> values) {
2225     if (values != null) {
2226       ObjectMapper objectMapper = new ObjectMapper();
2227       ArrayNode valuesJson = objectMapper.createArrayNode();
2228       for (String value : values) {
2229         valuesJson.add(value);
2230       }
2231       objectNode.set(fieldName, valuesJson);
2232     }
2233   }
2234   
2235   
2236   /**
2237    * get a field as string and handle null
2238    * @param jsonNode
2239    * @param fieldName
2240    * @return the string
2241    */
2242   public static String jsonJacksonGetString(JsonNode jsonNode, String fieldName, String defaultString) {
2243     if (jsonNode != null) {
2244       JsonNode fieldNode = jsonNode.get(fieldName);
2245       if (fieldNode != null) {
2246         if (!(fieldNode instanceof NullNode)) {
2247           if (defaultString != null) {
2248             return fieldNode.asText(defaultString);
2249           }
2250           return fieldNode.asText();
2251         }
2252       }
2253     }
2254     return defaultString;
2255   }
2256 
2257   /**
2258    * get a field as node or array.  could return null if not there
2259    * @param jsonNode
2260    * @param fieldName
2261    * @return the node or array
2262    */
2263   public static JsonNode jsonJacksonGetNode(JsonNode jsonNode, String fieldName) {
2264     
2265     if (jsonNode == null) {
2266       return null;
2267     }
2268     
2269     JsonNode fieldNode = jsonNode.get(fieldName);
2270     if (fieldNode == null || fieldNode instanceof NullNode) {
2271       return null;
2272     }
2273     return fieldNode;
2274     
2275   }
2276   
2277   /**
2278    * get a field as string set.  could return null if not there
2279    * @param jsonNode
2280    * @param fieldName
2281    * @return the string
2282    */
2283   public static Set<String> jsonJacksonGetStringSet(JsonNode jsonNode, String fieldName) {
2284     Set<String> result = null;
2285     if (jsonNode != null) {
2286       ArrayNode fieldNode = (ArrayNode)jsonNode.get(fieldName);
2287       if (fieldNode != null) {
2288         result = new LinkedHashSet<String>();
2289         for (int i=0;i<fieldNode.size();i++) {
2290           JsonNode textNode = fieldNode.get(i);
2291           String textValue = textNode.asText();
2292           result.add(textValue);
2293         }
2294       }
2295     }
2296     return result;
2297   }
2298 
2299   /**
2300    * get a field as boolean and handle null
2301    * @param jsonNode
2302    * @param fieldName
2303    * @return the string
2304    */
2305   public static Boolean jsonJacksonGetBoolean(JsonNode jsonNode, String fieldName) {
2306     return jsonJacksonGetBoolean(jsonNode, fieldName, null);
2307   }
2308   
2309   /**
2310    * get a field as boolean and handle null
2311    * @param jsonNode
2312    * @param fieldName
2313    * @param defaultBoolean if null use this value
2314    * @return the string
2315    */
2316   public static Boolean jsonJacksonGetBoolean(JsonNode jsonNode, String fieldName, Boolean defaultBoolean) {
2317     if (jsonNode != null) {
2318       JsonNode fieldNode = jsonNode.get(fieldName);
2319       if (fieldNode != null) {
2320         if (!(fieldNode instanceof NullNode)) {
2321           if (defaultBoolean != null) {
2322             return fieldNode.asBoolean(defaultBoolean);
2323           }
2324           return fieldNode.asBoolean();
2325         }
2326       }
2327     }
2328     return defaultBoolean;
2329   }
2330 
2331   /**
2332    * get a field as long and handle null
2333    * @param jsonNode
2334    * @param fieldName
2335    * @return the string
2336    */
2337   public static Long jsonJacksonGetLong(JsonNode jsonNode, String fieldName) {
2338     return jsonJacksonGetLong(jsonNode, fieldName, null);
2339   }
2340 
2341   /**
2342    * get a field as long and handle null
2343    * @param jsonNode
2344    * @param fieldName
2345    * @return the string
2346    */
2347   public static Long jsonJacksonGetLong(JsonNode jsonNode, String fieldName, Long defaultLong) {
2348     if (jsonNode != null) {
2349       JsonNode fieldNode = jsonNode.get(fieldName);
2350       if (fieldNode != null) {
2351         if (!(fieldNode instanceof NullNode)) {
2352           if (defaultLong != null) {
2353             return fieldNode.asLong(defaultLong);
2354           }
2355           return fieldNode.asLong();
2356         }
2357       }
2358     }
2359     return defaultLong;
2360   }
2361 
2362   /**
2363    * get a field as integer and handle null
2364    * @param jsonNode
2365    * @param fieldName
2366    * @return the string
2367    */
2368   public static Integer jsonJacksonGetInteger(JsonNode jsonNode, String fieldName) {
2369     return jsonJacksonGetInteger(jsonNode, fieldName, null);
2370   }
2371 
2372   /**
2373    * get a field as integer and handle null
2374    * @param jsonNode
2375    * @param fieldName
2376    * @return the string
2377    */
2378   public static Integer jsonJacksonGetInteger(JsonNode jsonNode, String fieldName, Integer defaultInteger) {
2379     if (jsonNode != null) {
2380       JsonNode fieldNode = jsonNode.get(fieldName);
2381       if (fieldNode != null) {
2382         if (!(fieldNode instanceof NullNode)) {
2383           if (defaultInteger != null) {
2384             return fieldNode.asInt(defaultInteger);
2385           }
2386           return fieldNode.asInt();
2387         }
2388       }
2389     }
2390     return defaultInteger;
2391   }
2392 
2393 
2394   public static ObjectNode jsonJacksonNode() {
2395     ObjectMapper objectMapper = new ObjectMapper();
2396     ObjectNode objectNode = objectMapper.createObjectNode();
2397     return objectNode;
2398   }
2399 
2400   public static ArrayNode jsonJacksonArrayNode() {
2401     ObjectMapper objectMapper = new ObjectMapper();
2402     ArrayNode arrayNode = objectMapper.createArrayNode();
2403     return arrayNode;
2404   }
2405   
2406  
2407   
2408   public static JsonNode jsonJacksonNode(String json) {
2409     try {
2410       ObjectMapper objectMapper = new ObjectMapper();
2411       //read JSON like DOM Parser
2412       JsonNode rootNode = objectMapper.readTree(json);
2413       return rootNode;
2414     } catch (Exception e) {
2415       injectInException(e, "Error in json '" + abbreviate(json, 4000) + "'");
2416       if (e instanceof RuntimeException) {
2417         throw (RuntimeException)e;
2418       }
2419       throw new RuntimeException(e);
2420     }
2421   }
2422 
2423   public static String jsonJacksonToString(JsonNode jsonNode) {
2424     try {
2425       ObjectMapper objectMapper = new ObjectMapper();
2426       String json = objectMapper.writeValueAsString(jsonNode);
2427       return json;
2428     } catch (Exception e) {
2429       if (e instanceof RuntimeException) {
2430         throw (RuntimeException)e;
2431       }
2432       throw new RuntimeException(e);
2433     }
2434   }
2435 
2436   
2437   
2438   /**
2439    * convert an object to json without wrapping it with the simple class name.
2440    * @param object
2441    * @return the string of json
2442    */
2443   public static String jsonConvertToNoWrap(Object object) {
2444 	  //TODO call the other jsonConvertTo() method
2445 	    if (object == null) {
2446 	      throw new NullPointerException();
2447 	    }
2448 
2449 	    JsonConfig jsonConfig = new JsonConfig();
2450 	    jsonConfig.setJsonPropertyFilter( new PropertyFilter(){
2451 	       public boolean apply( Object source, String name, Object value ) {
2452 	         //json-lib cannot handle maps where the key is not a string
2453 	         if( value != null && value instanceof Map ){
2454 	           Map map = (Map)value;
2455 	           if (map.size() > 0 && !(map.keySet().iterator().next() instanceof String)) {
2456 	             return true;
2457 	           }
2458 	         }
2459            if ("source".equals(name) && source instanceof Subject) {
2460              return true;
2461            }
2462 	         if ("subject".equals(name) && source != null && source.getClass().getName().equals("edu.internet2.middleware.grouper.grouperUi.beans.api.GuiSubject")) {
2463 	           return true;
2464 	         }
2465            if ("member".equals(name) && source != null && source.getClass().getName().equals("edu.internet2.middleware.grouper.grouperUi.beans.api.GuiSubject")) {
2466              return true;
2467            }
2468            return value == null;
2469 	       }
2470 	    });
2471 	    JSONObject jsonObject = JSONObject.fromObject( object, jsonConfig );
2472 	    String json = jsonObject.toString();
2473 
2474 	    return json;
2475 	  }
2476 
2477   /**
2478    * convert an object to json.  note this wraps the gson with the object simple name so it can be revived
2479    * @param object
2480    * @param writer
2481    */
2482   public static void jsonConvertTo(Object object, Writer writer) {
2483     String json = jsonConvertTo(object);
2484     try {
2485       writer.write(json);
2486     } catch (IOException ioe) {
2487       throw new RuntimeException(ioe);
2488     }
2489   }
2490 
2491   /**
2492    * <pre>
2493    * detects the front of a json string, pops off the first field, and gives the body as the matcher
2494    * ^\s*\{\s*\"([^"]+)\"\s*:\s*\{(.*)}$
2495    * Example matching text:
2496    * {
2497    *  "XstreamPocGroup":{
2498    *    "somethingNotMarshaled":"whatever",
2499    *    "name":"myGroup",
2500    *    "someInt":5,
2501    *    "someBool":true,
2502    *    "members":[
2503    *      {
2504    *        "name":"John",
2505    *        "description":"John Smith - Employee"
2506    *      },
2507    *      {
2508    *        "name":"Mary",
2509    *        "description":"Mary Johnson - Student"
2510    *      }
2511    *    ]
2512    *  }
2513    * }
2514    *
2515    * ^\s*          front of string and optional space
2516    * \{\s*         open bracket and optional space
2517    * \"([^"]+)\"   quote, simple name of class, quote
2518    * \s*:\s*       optional space, colon, optional space
2519    * \{(.*)}\s*$      open bracket, the class info, close bracket, optional space, end of string
2520    *
2521    *
2522    * </pre>
2523    */
2524   private static Pattern jsonPattern = Pattern.compile("^\\s*\\{\\s*\\\"([^\"]+)\\\"\\s*:\\s*(.*)}\\s*$", Pattern.DOTALL);
2525 
2526   /**
2527    * convert an object from json.  note this works well if there are no collections, just real types, arrays, etc.
2528    * @param conversionMap is the class simple name to class of objects which are allowed to be brought back.
2529    * Note: only the top level object needs to be registered
2530    * @param json
2531    * @return the object
2532    */
2533   public static Object jsonConvertFrom(Map<String, Class<?>> conversionMap, String json) {
2534 
2535     //gson does not put the type of the object in the json, but we need that.  so when we convert,
2536     //put the type in there.  So we need to extract the type out when unmarshaling
2537     Matcher matcher = jsonPattern.matcher(json);
2538 
2539     if (!matcher.matches()) {
2540       throw new RuntimeException("Cant match this json, should start with simple class name: " + json);
2541     }
2542 
2543     String simpleClassName = matcher.group(1);
2544     String jsonBody = matcher.group(2);
2545 
2546     Class<?> theClass = conversionMap.get(simpleClassName);
2547     if (theClass == null) {
2548       throw new RuntimeException("Not allowed to unmarshal json: " + simpleClassName + ", " + json);
2549     }
2550 //    Gson gson = new GsonBuilder().create();
2551 //    Object object = gson.fromJson(jsonBody, theClass);
2552     JSONObject jsonObject = JSONObject.fromObject( jsonBody );
2553     Object object = JSONObject.toBean( jsonObject, theClass );
2554 
2555     return object;
2556   }
2557   /**
2558    * convert an object from json.  note this works well if there are no collections, just real types, arrays, etc.
2559    * @param json is the json string, not wrapped with a simple class name
2560    * @param theClass is the class that the object should be coverted into.
2561    * Note: only the top level object needs to be registered
2562    * @return the object
2563    * 
2564    */
2565   public static <T> T jsonConvertFrom (String json, Class<T> theClass) {
2566 	  	JSONObject jsonObject = JSONObject.fromObject( json );
2567 	    Object object = JSONObject.toBean( jsonObject, theClass );
2568 	    return (T)object;
2569   }
2570   
2571   /**
2572    * get the extension from name.  if name is a:b:c, name is c
2573    * @param name
2574    * @return the name
2575    */
2576   public static String extensionFromName(String name) {
2577     if (isBlank(name)) {
2578       return name;
2579     }
2580     int lastColonIndex = name.lastIndexOf(':');
2581     if (lastColonIndex == -1) {
2582       return name;
2583     }
2584     String extension = name.substring(lastColonIndex+1);
2585     return extension;
2586   }
2587 
2588   /**
2589    * <pre>
2590    * see if a name is in a folder (not subfolder).  if name is a:b:c, and folder is a:b, then yes
2591    * if a:b:c and a:c, then no
2592    * if a:b:c and a, then no
2593    * </pre>
2594    * @param name
2595    * @param folder 
2596    * @return the name
2597    */
2598   public static boolean nameInFolderDirect(String name, String folder) {
2599 
2600     String parentStem = parentStemNameFromName(name);
2601 
2602     return equals(parentStem, folder);
2603   }
2604 
2605   /**
2606    * <pre>Returns the class object.</pre>
2607    * @param origClassName is fully qualified
2608    * @return the class
2609    */
2610   public static Class forName(String origClassName) {
2611 
2612     try {
2613       return Class.forName(origClassName);
2614     } catch (Throwable t) {
2615       throw new RuntimeException("Problem loading class: " + origClassName, t);
2616     }
2617 
2618   }
2619 
2620   /**
2621    * Construct a class
2622    * @param <T> template type
2623    * @param theClass
2624    * @return the instance
2625    */
2626   public static <T> T newInstance(Class<T> theClass) {
2627     try {
2628       return theClass.newInstance();
2629     } catch (Throwable e) {
2630       if (theClass != null && Modifier.isAbstract(theClass.getModifiers())) {
2631         throw new RuntimeException("Problem with class: " + theClass + ", maybe because it is abstract!", e);
2632       }
2633       throw new RuntimeException("Problem with class: " + theClass, e);
2634     }
2635   }
2636 
2637   /**
2638    * get the parent stem name from name.  if already a root stem
2639    * then just return null.  e.g. if the name is a:b:c then
2640    * the return value is a:b
2641    * @param name
2642    * @return the parent stem name or null if none
2643    */
2644   public static String parentStemNameFromName(String name) {
2645     return parentStemNameFromName(name, true);
2646   }
2647   /**
2648    * get the parent stem name from name.  if already a root stem
2649    * then just return null.  e.g. if the name is a:b:c then
2650    * the return value is a:b
2651    * @param name
2652    * @param nullForRoot null for root, otherwise colon
2653    * @return the parent stem name or null if none
2654    */
2655   public static String parentStemNameFromName(String name, boolean nullForRoot) {
2656 
2657     //null safe
2658     if (isBlank(name)) {
2659       return name;
2660     }
2661 
2662     int lastColonIndex = name.lastIndexOf(':');
2663     if (lastColonIndex == -1) {
2664 
2665       if (nullForRoot) {
2666       return null;
2667       }
2668       return ":";
2669     }
2670     String parentStemName = name.substring(0,lastColonIndex);
2671     return parentStemName;
2672 
2673   }
2674 
2675   /**
2676    * return the string or the other if the first is blank
2677    * @param string
2678    * @param defaultStringIfBlank
2679    * @return the string or the default one
2680    */
2681   public static String defaultIfBlank(String string, String defaultStringIfBlank) {
2682     return isBlank(string) ? defaultStringIfBlank : string;
2683   }
2684 
2685   /**
2686    * genericized method to see if first is null, if so then return second, else first.
2687    * @param <T>
2688    * @param theValue first input
2689    * @param defaultIfTheValueIsNull second input
2690    * @return the first if not null, second if no
2691    */
2692   public static <T> T defaultIfNull(T theValue, T defaultIfTheValueIsNull) {
2693     return theValue != null ? theValue : defaultIfTheValueIsNull;
2694   }
2695 
2696   /**
2697    * add each element of listToAdd if it is not already in list
2698    * @param <T>
2699    * @param list to add to
2700    * @param listToAdd each element will be added to list, or null if none
2701    */
2702   public static <T> void addIfNotThere(Collection<T> list, Collection<T> listToAdd) {
2703     //maybe nothing to do
2704     if (listToAdd == null) {
2705       return;
2706     }
2707     for (T t : listToAdd) {
2708       if (!list.contains(t)) {
2709         list.add(t);
2710       }
2711     }
2712   }
2713 
2714 
2715   /**
2716    * print out various types of objects
2717    *
2718    * @param object
2719    * @param maxChars is where it should stop when figuring out object.  note, result might be longer than max...
2720    * need to abbreviate when back
2721    * @param result is where to append to
2722    */
2723   private static void toStringForLogHelper(Object object, int maxChars, StringBuilder result, boolean newLines) {
2724 
2725     try {
2726       if (object == null) {
2727         result.append("null");
2728       } else if (object.getClass().isArray()) {
2729         // handle arrays
2730         int length = Array.getLength(object);
2731         if (length == 0) {
2732           result.append("Empty array");
2733         } else {
2734           result.append("Array size: ").append(length).append(": ");
2735           for (int i = 0; i < length; i++) {
2736             result.append("[").append(i).append("]: ").append(
2737                 toStringForLog(Array.get(object, i), maxChars)).append(newLines ? "\n" : ", ");
2738             if (maxChars != -1 && result.length() > maxChars) {
2739               return;
2740             }
2741           }
2742         }
2743       } else if (object instanceof Collection) {
2744         //give size and type if collection
2745         Collection<Object> collection = (Collection<Object>) object;
2746         int collectionSize = collection.size();
2747         if (collectionSize == 0) {
2748           result.append("Empty ").append(object.getClass().getSimpleName());
2749         } else {
2750           result.append(object.getClass().getSimpleName()).append(" size: ").append(collectionSize).append(": ");
2751           int i=0;
2752           for (Object collectionObject : collection) {
2753             result.append("[").append(i).append("]: ").append(
2754                 toStringForLog(collectionObject, maxChars)).append(newLines ? "\n" : ", ");
2755             if (maxChars != -1 && result.length() > maxChars) {
2756               return;
2757             }
2758             i++;
2759           }
2760         }
2761       } else if (object instanceof Subject) {
2762         result.append(subjectToString((Subject)object));
2763       } else {
2764         result.append(object.toString());
2765       }
2766     } catch (Exception e) {
2767       result.append("<<exception>> ").append(object.getClass()).append(":\n")
2768         .append(getFullStackTrace(e)).append("\n");
2769     }
2770   }
2771 
2772   /**
2773    * print out an object by fields
2774    * @param object
2775    * @param fieldNames
2776    * @return the string representation or null if null
2777    */
2778   public static String toStringFields(Object object, Set<String> fieldNames) {
2779 
2780     if (object == null) {
2781       return null;
2782     }
2783 
2784     StringBuilder result = new StringBuilder(object.getClass().getSimpleName() + ": ");
2785 
2786     //loop through fields
2787     for (String fieldName : nonNull(fieldNames)) {
2788 
2789       Object value = fieldValue(object, fieldName);
2790       if (value != null) {
2791         result.append(fieldName).append(": '").append(value).append("', ");
2792       }
2793 
2794     }
2795 
2796     //take off last comma (assume there was at least one field)
2797     result.delete(result.length()-2, result.length());
2798     return result.toString();
2799 
2800   }
2801 
2802   /**
2803    * convert a set to a string (comma separate)
2804    * @param collection
2805    * @return the String
2806    */
2807   public static String collectionToString(Collection collection) {
2808     if (collection == null) {
2809       return "null";
2810     }
2811     if (collection.size() == 0) {
2812       return "empty";
2813     }
2814     StringBuilder result = new StringBuilder();
2815     boolean first = true;
2816     for (Object object : collection) {
2817       if (!first) {
2818         result.append(", ");
2819       }
2820       first = false;
2821       result.append(object);
2822     }
2823     return result.toString();
2824 
2825   }
2826   /**
2827    * convert a set to a string (comma separate)
2828    * @param set
2829    * @return the String
2830    */
2831   public static String setToString(Set set) {
2832     return collectionToString(set);
2833   }
2834 
2835   /**
2836    * convert a set to a string (comma separate)
2837    * @param map
2838    * @return the String
2839    * @deprecated use mapToString(map)
2840    */
2841   @Deprecated
2842   public static String MapToString(Map map) {
2843     return mapToString(map);
2844   }
2845 
2846   /**
2847    * convert a set to a string (comma separate)
2848    * @param map
2849    * @return the String
2850    */
2851   public static String mapToString(Map map) {
2852     if (map == null) {
2853       return "null";
2854     }
2855     if (map.size() == 0) {
2856       return "empty";
2857     }
2858     StringBuilder result = new StringBuilder();
2859     boolean first = true;
2860     for (Object object : map.keySet()) {
2861       if (!first) {
2862         result.append(", ");
2863       }
2864       first = false;
2865       result.append(object).append(": ").append(map.get(object));
2866     }
2867     return result.toString();
2868   }
2869 
2870   /**
2871    * print out various types of objects
2872    *
2873    * @param object
2874    * @return the string value
2875    */
2876   public static String toStringForLog(Object object) {
2877     StringBuilder result = new StringBuilder();
2878     toStringForLogHelper(object, -1, result, true);
2879     return result.toString();
2880   }
2881 
2882   /**
2883    * print out various types of objects
2884    *
2885    * @param object
2886    * @return the string value
2887    */
2888   public static String toStringForLog(Object object, boolean newLines) {
2889     StringBuilder result = new StringBuilder();
2890     toStringForLogHelper(object, -1, result, newLines);
2891     return result.toString();
2892   }
2893 
2894   /**
2895    * print out various types of objects
2896    *
2897    * @param object
2898    * @param maxChars is the max chars that should be returned (abbreviate if longer), or -1 for any amount
2899    * @return the string value
2900    */
2901   public static String toStringForLog(Object object, int maxChars) {
2902     StringBuilder result = new StringBuilder();
2903     toStringForLogHelper(object, -1, result, true);
2904     String resultString = result.toString();
2905     if (maxChars != -1) {
2906       return abbreviate(resultString, maxChars);
2907     }
2908     return resultString;
2909   }
2910 
2911   /**
2912    * If batching this is the number of batches
2913    * @param count is size of set
2914    * @param batchSize
2915    * @return the number of batches
2916    */
2917   public static int batchNumberOfBatches(int count, int batchSize) {
2918     //not sure why this would be 0...
2919     if (batchSize == 0) {
2920       return 0;
2921     }
2922     int batches = 1 + ((count - 1) / batchSize);
2923     return batches;
2924 
2925   }
2926 
2927   /**
2928    * If batching this is the number of batches
2929    * @param collection
2930    * @param batchSize
2931    * @return the number of batches
2932    */
2933   public static int batchNumberOfBatches(Collection<?> collection, int batchSize) {
2934     int arrraySize = length(collection);
2935     return batchNumberOfBatches(arrraySize, batchSize);
2936 
2937   }
2938 
2939   /**
2940    * if the groups are: a:b:c:d and a:d:r, then return the strings:
2941    * :, a, a:b, a:b:c, a:d
2942    *
2943    * @param groups
2944    * @return the set of stem names
2945    */
2946   public static Set<String> findParentStemNames(Collection<Group> groups) {
2947     Set<String> result = new LinkedHashSet<String>();
2948     if (groups == null || groups.size() == 0) {
2949       return result;
2950     }
2951     for (Group group : groups) {
2952       String name = group.getName();
2953       result.addAll(findParentStemNames(name));
2954     }
2955     return result;
2956   }
2957 
2958   /**
2959    * if the groups are: a:b:c:d, then return the strings:
2960    * :, a, a:b, a:b:c
2961    *
2962    * @param objectName
2963    * @return the set of stem names
2964    */
2965   public static Set<String> findParentStemNames(String objectName) {
2966     List<String> result = new ArrayList<String>();
2967     String currentName = objectName;
2968     while(true) {
2969       currentName = parentStemNameFromName(currentName);
2970       if (isEmpty(currentName)) {
2971         //add root
2972         result.add(":");
2973         break;
2974       }
2975       result.add(currentName);
2976     }
2977     Collections.reverse(result);
2978     return new LinkedHashSet(result);
2979   }
2980 
2981   /**
2982    * retrieve a batch by 0 index. Will return an array of size batchSize or
2983    * the remainder. the array will be full of elements. Note, this requires an
2984    * ordered input (so use linkedhashset not hashset if doing sets)
2985    * @param <T> template type
2986    * @param collection
2987    * @param batchSize
2988    * @param batchIndex
2989    * @return the list
2990    *         This never returns null, only empty list
2991    */
2992   @SuppressWarnings("unchecked")
2993   public static <T> List<T> batchList(List<T> collection, int batchSize,
2994       int batchIndex) {
2995 
2996     int numberOfBatches = batchNumberOfBatches(collection, batchSize);
2997     int arraySize = length(collection);
2998 
2999     // short circuit
3000     if (arraySize == 0) {
3001       return new ArrayList<T>();
3002     }
3003 
3004     List<T> theBatchObjects = new ArrayList<T>();
3005 
3006     // lets get the type of the first element if possible
3007 //    Object first = get(arrayOrCollection, 0);
3008 //
3009 //    Class theType = first == null ? Object.class : first.getClass();
3010 
3011     // if last batch
3012     if (batchIndex == numberOfBatches - 1) {
3013 
3014       // needs to work to 1-n
3015       //int thisBatchSize = 1 + ((arraySize - 1) % batchSize);
3016 
3017       int collectionIndex = 0;
3018       for (T t : collection) {
3019         if (collectionIndex++ < batchIndex * batchSize) {
3020           continue;
3021         }
3022         //just copy the rest
3023         //if (collectionIndex >= (batchIndex * batchSize) + arraySize) {
3024         //  break;
3025         //}
3026         //we are in the copy mode
3027         theBatchObjects.add(t);
3028       }
3029 
3030     } else {
3031       // if non-last batch
3032       //int newIndex = 0;
3033       int collectionIndex = 0;
3034       for (T t : collection) {
3035         if (collectionIndex < batchIndex * batchSize) {
3036           collectionIndex++;
3037           continue;
3038         }
3039         //done with batch
3040         if (collectionIndex >= (batchIndex + 1) * batchSize) {
3041           break;
3042         }
3043         theBatchObjects.add(t);
3044         collectionIndex++;
3045       }
3046     }
3047     return theBatchObjects;
3048   }
3049   /**
3050    * split a string based on a separator into an array, and trim each entry (see
3051    * the Commons Util trim() for more details)
3052    *
3053    * @param input
3054    *          is the delimited input to split and trim
3055    * @param separator
3056    *          is what to split on
3057    *
3058    * @return the array of items after split and trimmed, or null if input is null.  will be trimmed to empty
3059    */
3060   public static String[] splitTrim(String input, String separator) {
3061     return splitTrim(input, separator, true);
3062   }
3063 
3064   /**
3065    * split a string based on a separator into an array, and trim each entry (see
3066    * the Commons Util trim() for more details)
3067    *
3068    * @param input
3069    *          is the delimited input to split and trim
3070    * @param separator
3071    *          is what to split on
3072    *
3073    * @return the list of items after split and trimmed, or null if input is null.  will be trimmed to empty
3074    */
3075   public static List<String> splitTrimToList(String input, String separator) {
3076     if (isBlank(input)) {
3077       return null;
3078     }
3079     String[] array =  splitTrim(input, separator);
3080     return toList(array);
3081   }
3082 
3083   /**
3084    * split a string based on a separator into an array, and trim each entry (see
3085    * the Commons Util trim() for more details)
3086    *
3087    * @param input
3088    *          is the delimited input to split and trim
3089    * @param separator
3090    *          is what to split on
3091    *
3092    * @return the set of items after split and trimmed, or null if input is null.  will be trimmed to empty
3093    */
3094   public static Set<String> splitTrimToSet(String input, String separator) {
3095     if (isBlank(input)) {
3096       return null;
3097     }
3098     String[] array =  splitTrim(input, separator);
3099     return toSet(array);
3100   }
3101 
3102   /**
3103    * split a string based on a separator into an array, and trim each entry (see
3104    * the Commons Util trim() for more details)
3105    *
3106    * @param input
3107    *          is the delimited input to split and trim
3108    * @param separator
3109    *          is what to split on
3110    * @param treatAdjacentSeparatorsAsOne
3111    * @return the array of items after split and trimmed, or null if input is null.  will be trimmed to empty
3112    */
3113   public static String[] splitTrim(String input, String separator, boolean treatAdjacentSeparatorsAsOne) {
3114     if (isBlank(input)) {
3115       return null;
3116     }
3117 
3118     //first split
3119     String[] items = treatAdjacentSeparatorsAsOne ? splitByWholeSeparator(input, separator) :
3120       split(input, separator);
3121 
3122     //then trim
3123     for (int i = 0; (items != null) && (i < items.length); i++) {
3124       items[i] = trim(items[i]);
3125     }
3126 
3127     //return the array
3128     return items;
3129   }
3130 
3131   /**
3132    * escape url chars (e.g. a # is %23)
3133    * @param string input
3134    * @return the encoded string
3135    */
3136   public static String escapeUrlEncode(String string) {
3137     String result = null;
3138     try {
3139       result = URLEncoder.encode(string, "UTF-8");
3140     } catch (UnsupportedEncodingException ex) {
3141       throw new RuntimeException("UTF-8 not supported", ex);
3142     }
3143     //i dont think colon is needed to url escape, and it makes urls
3144     //better looking when stem name is in it...
3145     result = replace(result, "%3A", ":");
3146     return result;
3147   }
3148 
3149   /**
3150    * unescape url chars (e.g. a space is %20)
3151    * @param string input
3152    * @return the encoded string
3153    */
3154   public static String escapeUrlDecode(String string) {
3155     String result = null;
3156     try {
3157       result = URLDecoder.decode(string, "UTF-8");
3158     } catch (UnsupportedEncodingException ex) {
3159       throw new RuntimeException("UTF-8 not supported", ex);
3160     }
3161     return result;
3162   }
3163 
3164   /**
3165    * make sure a list is non null.  If null, then return an empty list
3166    * @param <T>
3167    * @param list
3168    * @return the list or empty list if null
3169    */
3170   public static <T> List<T> nonNull(List<T> list) {
3171     return list == null ? new ArrayList<T>() : list;
3172   }
3173 
3174   /**
3175    * make sure a collection is non null.  If null, then return an empty list
3176    * @param <T>
3177    * @param list
3178    * @return the list or empty list if null
3179    */
3180   public static <T> Collection<T> nonNull(Collection<T> list) {
3181     return list == null ? new ArrayList<T>() : list;
3182   }
3183 
3184   /**
3185    * make sure a list is non null.  If null, then return an empty set
3186    * @param <T>
3187    * @param set
3188    * @return the set or empty set if null
3189    */
3190   public static <T> Set<T> nonNull(Set<T> set) {
3191     return set == null ? new HashSet<T>() : set;
3192   }
3193 
3194   /**
3195    * make sure it is non null, if null, then give new map
3196    *
3197    * @param <K> key of map
3198    * @param <V> value of map
3199    * @param map is map
3200    * @return set non null
3201    */
3202   public static <K,V> Map<K,V> nonNull(Map<K,V> map) {
3203     return map == null ? new HashMap<K,V>() : map;
3204   }
3205 
3206   /**
3207    * return a list of objects from varargs.  Though if there is one
3208    * object, and it is a list, return it.
3209    *
3210    * @param <T>
3211    *            template type of the objects
3212    * @param objects
3213    * @return the list or null if objects is null
3214    */
3215   public static <T> List<T> toList(T... objects) {
3216     if (objects == null) {
3217       return null;
3218     }
3219     if (objects.length == 1 && objects[0] instanceof List) {
3220       return (List<T>)objects[0];
3221     }
3222 
3223     List<T> result = new ArrayList<T>();
3224     for (T object : objects) {
3225       result.add(object);
3226     }
3227     return result;
3228   }
3229 
3230   /**
3231    * return a list of objects from varargs.  Though if there is one
3232    * object, and it is a list, return it.
3233    *
3234    *            template type of the objects
3235    * @param objects
3236    * @return the list or null if objects is null
3237    */
3238   public static List<Object> toListObject(Object... objects) {
3239     if (objects == null) {
3240       return null;
3241     }
3242     List<Object> result = new ArrayList<Object>();
3243     for (Object object : objects) {
3244       result.add(object);
3245     }
3246     return result;
3247   }
3248 
3249 
3250   /**
3251    * convert classes to a list
3252    * @param classes
3253    * @return list of classes
3254    */
3255   public static List<Class<?>> toListClasses(Class<?>... classes) {
3256     return toList(classes);
3257   }
3258 
3259   /**
3260    * return a set of objects from varargs.
3261    *
3262    * @param <T> template type of the objects
3263    * @param objects
3264    * @return the set
3265    */
3266   public static <T> Set<T> toSet(T... objects) {
3267     if (objects == null || objects.length == 0 || (objects.length == 1 && objects[0] == null )) {
3268       return null;
3269     }
3270     Set<T> result = new LinkedHashSet<T>();
3271     for (T object : objects) {
3272       result.add(object);
3273     }
3274     return result;
3275   }
3276 
3277   /**
3278    * return a set of objects from varargs.
3279    *
3280    * @param <T> template type of the objects
3281    * @param objects
3282    * @return the set
3283    */
3284   public static <T> Set<Object> toSetObjectType(T... objects) {
3285     if (objects == null || objects.length == 0 || (objects.length == 1 && objects[0] == null )) {
3286       return null;
3287     }
3288     Set<Object> result = new LinkedHashSet<Object>();
3289     for (T object : objects) {
3290       result.add(object);
3291     }
3292     return result;
3293   }
3294 
3295 
3296   /**
3297    * return a set of string
3298    *
3299    * @param <T> template type of the objects
3300    * @param object
3301    * @return the set
3302    */
3303   public static <T> Set<T> toSetObject(T object) {
3304     if (object == null) {
3305       return null;
3306     }
3307     Set<T> result = new LinkedHashSet<T>();
3308     result.add(object);
3309     return result;
3310   }
3311 
3312   /**
3313    * cache separator
3314    */
3315   private static final String CACHE_SEPARATOR = "__";
3316 
3317   /**
3318    * 
3319    * @param timestamp
3320    * @return timestamp string
3321    */
3322   public static String timestampIsoUtcSecondsConvertToString(Timestamp timestamp) {
3323     
3324     if (timestamp == null) {
3325       return null;
3326     }
3327     
3328     String my8601formattedDate = timestampIsoUtcSeconds.format(timestamp);
3329     return my8601formattedDate;
3330   }
3331 
3332   /**
3333    * 
3334    * @param timestamp
3335    * @return timestamp string
3336    */
3337   public static Timestamp timestampIsoUtcSecondsConvertFromString(String string) {
3338     
3339     if (StringUtils.isBlank(string)) {
3340       return null;
3341     }
3342     
3343     try {
3344       Date date = timestampIsoUtcSeconds.parse(string);
3345       return new Timestamp(date.getTime());
3346     } catch (Exception e) {
3347       throw new RuntimeException("Cant parse string: '" + string + "' in format: yyyy-MM-dd'T'HH:mm:ss'Z'");
3348     }
3349   }
3350 
3351   /**
3352    * 
3353    */
3354   public static final DateFormat timestampIsoUtcSeconds = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
3355   
3356   static {
3357     timestampIsoUtcSeconds.setTimeZone(TimeZone.getTimeZone("UTC"));
3358   }
3359   
3360   /**
3361    * string format of dates
3362    */
3363   public static final String DATE_FORMAT = "yyyyMMdd";
3364 
3365   /**
3366    * string format of dates for file names
3367    */
3368   public static final String TIMESTAMP_FILE_FORMAT = "yyyy_MM_dd__HH_mm_ss_SSS";
3369 
3370   /**
3371    * timestamp format, make sure to synchronize
3372    */
3373   final static SimpleDateFormat timestampFileFormat = new SimpleDateFormat(TIMESTAMP_FILE_FORMAT);
3374 
3375   /**
3376    * string format of dates
3377    */
3378   public static final String DATE_FORMAT2 = "yyyy/MM/dd";
3379 
3380   /**
3381    * format including minutes and seconds: yyyy/MM/dd HH:mm:ss
3382    */
3383   public static final String DATE_MINUTES_SECONDS_FORMAT = "yyyy/MM/dd HH:mm:ss";
3384 
3385   /**
3386    * format including minutes and seconds: yyyyMMdd HH:mm:ss
3387    */
3388   public static final String DATE_MINUTES_SECONDS_NO_SLASH_FORMAT = "yyyyMMdd HH:mm:ss";
3389 
3390   /**
3391    * format on screen of config for milestone: yyyy/MM/dd HH:mm:ss.SSS
3392    */
3393   public static final String TIMESTAMP_FORMAT = "yyyy/MM/dd HH:mm:ss.SSS";
3394 
3395   /**
3396    * format on screen of config for milestone: yyyyMMdd HH:mm:ss.SSS
3397    */
3398   public static final String TIMESTAMP_NO_SLASH_FORMAT = "yyyyMMdd HH:mm:ss.SSS";
3399 
3400   /**
3401    * date format, make sure to synchronize
3402    */
3403   final static SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
3404 
3405   /**
3406    * date format, make sure to synchronize
3407    */
3408   final static SimpleDateFormat dateFormat2 = new SimpleDateFormat(DATE_FORMAT2);
3409 
3410   /**
3411    * synchronize code that uses this standard formatter for dates with minutes and seconds
3412    */
3413   final static SimpleDateFormat dateMinutesSecondsFormat = new SimpleDateFormat(
3414       DATE_MINUTES_SECONDS_FORMAT);
3415 
3416   /**
3417    * synchronize code that uses this standard formatter for dates with minutes and seconds
3418    */
3419   final static SimpleDateFormat dateMinutesSecondsNoSlashFormat = new SimpleDateFormat(
3420       DATE_MINUTES_SECONDS_NO_SLASH_FORMAT);
3421 
3422   /**
3423    * <pre> format: yyyy/MM/dd HH:mm:ss.SSS synchronize code that uses this standard formatter for timestamps </pre>
3424    */
3425   final static SimpleDateFormat timestampFormat = new SimpleDateFormat(TIMESTAMP_FORMAT);
3426 
3427   /**
3428    * synchronize code that uses this standard formatter for timestamps
3429    */
3430   final static SimpleDateFormat timestampNoSlashFormat = new SimpleDateFormat(
3431       TIMESTAMP_NO_SLASH_FORMAT);
3432 
3433   /**
3434    * If false, throw an assertException, and give a reason
3435    *
3436    * @param isTrue
3437    * @param reason
3438    */
3439   public static void assertion(boolean isTrue, String reason) {
3440     if (!isTrue) {
3441       throw new RuntimeException(reason);
3442     }
3443 
3444   }
3445 
3446   /**
3447    * use the field cache, expire every day (just to be sure no leaks)
3448    */
3449   private static GrouperCache<String, Set<Field>> fieldSetCache = null;
3450 
3451   /**
3452    * synchronize on this object
3453    */
3454   private static Object fieldSetCacheSemaphore = new Object();
3455 
3456   /**
3457    * lazy load
3458    * @return field set cache
3459    */
3460   private static GrouperCache<String, Set<Field>> fieldSetCache() {
3461     if (fieldSetCache == null) {
3462       synchronized(fieldSetCacheSemaphore) {
3463         if (fieldSetCache == null) {
3464           fieldSetCache = new GrouperCache<String, Set<Field>>("edu.internet2.middleware.grouper.util.GrouperUtil.fieldSetCache",
3465               2000, false, 60*60*24, 60*60*24, false);
3466         }
3467       }
3468     }
3469     return fieldSetCache;
3470   }
3471 
3472 
3473   /**
3474    * make a cache with max size to cache declared methods
3475    */
3476   private static GrouperCache<Class, Method[]> declaredMethodsCache = null;
3477 
3478   /**
3479    * synchronize on this object
3480    */
3481   private static Object declaredMethodsCacheSemaphore = new Object();
3482 
3483   /**
3484    * lazy load
3485    * @return declared method cache
3486    */
3487   private static GrouperCache<Class, Method[]> declaredMethodsCache() {
3488     if (declaredMethodsCache == null) {
3489       synchronized(declaredMethodsCacheSemaphore) {
3490         if (declaredMethodsCache == null) {
3491           declaredMethodsCache = new GrouperCache<Class, Method[]>("edu.internet2.middleware.grouper.util.GrouperUtil.declaredMethodsCache",
3492               2000, false, 60*60*24, 60*60*24, false);
3493         }
3494       }
3495     }
3496     return declaredMethodsCache;
3497   }
3498 
3499 
3500 
3501   /**
3502    * use the field cache, expire every day (just to be sure no leaks)
3503    */
3504   private static GrouperCache<String, Set<Method>> getterSetCache = null;
3505 
3506   /**
3507    * synchronize on this object
3508    */
3509   private static Object getterSetCacheSemaphore = new Object();
3510 
3511   /**
3512    * lazy load
3513    * @return getter cache
3514    */
3515   private static GrouperCache<String, Set<Method>> getterSetCache() {
3516     if (getterSetCache == null) {
3517       synchronized(getterSetCacheSemaphore) {
3518         if (getterSetCache == null) {
3519           getterSetCache = new GrouperCache<String, Set<Method>>("edu.internet2.middleware.grouper.util.getterSetCache",
3520               2000, false, 0, 60*60*24, false);
3521         }
3522       }
3523     }
3524     return getterSetCache;
3525   }
3526 
3527 
3528 
3529   /**
3530    * use the field cache, expire every day (just to be sure no leaks)
3531    */
3532   private static GrouperCache<String, Set<Method>> setterSetCache = null;
3533 
3534   /**
3535    * synchronize on this object
3536    */
3537   private static Object setterSetCacheSemaphore = new Object();
3538 
3539   /**
3540    * lazy load
3541    * @return setter cache
3542    */
3543   private static GrouperCache<String, Set<Method>> setterSetCache() {
3544     if (setterSetCache == null) {
3545       synchronized(setterSetCacheSemaphore) {
3546         if (setterSetCache == null) {
3547           setterSetCache = new GrouperCache<String, Set<Method>>("edu.internet2.middleware.grouper.util.setterSetCache",
3548               2000, false, 0, 60*60*24, false);
3549         }
3550       }
3551     }
3552     return setterSetCache;
3553   }
3554 
3555 
3556   /**
3557    * Field lastId.
3558    */
3559   private static char[] lastId = convertLongToStringSmall(new Date().getTime())
3560       .toCharArray();
3561 
3562   /**
3563    * cache the properties read from resource
3564    */
3565   private static ExpirableCache<String, Properties> resourcePropertiesCache = null;
3566 
3567   /**
3568    * synchronize on this object
3569    */
3570   private static Object resourcePropertiesCacheSemaphore = new Object();
3571 
3572   /**
3573    * lazy load this since it prevents the class from loading
3574    * @return the cache
3575    */
3576   private static ExpirableCache<String, Properties> resourcePropertiesCache() {
3577     if (resourcePropertiesCache == null) {
3578       synchronized(resourcePropertiesCacheSemaphore) {
3579         if (resourcePropertiesCache == null) {
3580           //note, if this relies on the config file to configure, and the config file uses this, then we need a simpler cache here than an ehcache...
3581           //resourcePropertiesCache = new GrouperCache<String, Properties>(
3582           //  class.getName() + ".resourcePropertiesCache", 200, false, 300, 300, false);
3583           resourcePropertiesCache = new ExpirableCache<String, Properties>(5);
3584 
3585         }
3586       }
3587     }
3588     return resourcePropertiesCache;
3589   }
3590 
3591   /**
3592    * assign data to a field
3593    *
3594    * @param theClass
3595    *            the class which has the method
3596    * @param invokeOn
3597    *            to call on or null for static
3598    * @param fieldName
3599    *            method name to call
3600    * @param dataToAssign
3601    *            data
3602    * @param callOnSupers
3603    *            if static and method not exists, try on supers
3604    * @param overrideSecurity
3605    *            true to call on protected or private etc methods
3606    * @param typeCast
3607    *            true if we should typecast
3608    * @param annotationWithValueOverride
3609    *            annotation with value of override
3610    */
3611   public static void assignField(Class theClass, Object invokeOn,
3612       String fieldName, Object dataToAssign, boolean callOnSupers,
3613       boolean overrideSecurity, boolean typeCast,
3614       Class<? extends Annotation> annotationWithValueOverride) {
3615     if (theClass == null && invokeOn != null) {
3616       theClass = invokeOn.getClass();
3617     }
3618     Field field = field(theClass, fieldName, callOnSupers, true);
3619     assignField(field, invokeOn, dataToAssign, overrideSecurity, typeCast,
3620         annotationWithValueOverride);
3621   }
3622 
3623   /**
3624    * assign data to a field. Will find the field in superclasses, will
3625    * typecast, and will override security (private, protected, etc)
3626    *
3627    * @param theClass
3628    *            the class which has the method
3629    * @param invokeOn
3630    *            to call on or null for static
3631    * @param fieldName
3632    *            method name to call
3633    * @param dataToAssign
3634    *            data
3635    * @param annotationWithValueOverride
3636    *            annotation with value of override
3637    */
3638   public static void assignField(Class theClass, Object invokeOn,
3639       String fieldName, Object dataToAssign,
3640       Class<? extends Annotation> annotationWithValueOverride) {
3641     assignField(theClass, invokeOn, fieldName, dataToAssign, true, true,
3642         true, annotationWithValueOverride);
3643   }
3644 
3645   /**
3646    * assign data to a field
3647    *
3648    * @param field
3649    *            is the field to assign to
3650    * @param invokeOn
3651    *            to call on or null for static
3652    * @param dataToAssign
3653    *            data
3654    * @param overrideSecurity
3655    *            true to call on protected or private etc methods
3656    * @param typeCast
3657    *            true if we should typecast
3658    */
3659   public static void assignField(Field field, Object invokeOn,
3660       Object dataToAssign, boolean overrideSecurity, boolean typeCast) {
3661 
3662     try {
3663       Class fieldType = field.getType();
3664       // typecast
3665       if (typeCast) {
3666         dataToAssign =
3667                  typeCast(dataToAssign, fieldType,
3668                  true, true);
3669       }
3670       if (overrideSecurity) {
3671         field.setAccessible(true);
3672       }
3673       field.set(invokeOn, dataToAssign);
3674     } catch (Exception e) {
3675       throw new RuntimeException("Cant assign reflection field: "
3676           + (field == null ? null : field.getName()) + ", on: "
3677           + className(invokeOn) + ", with args: "
3678           + classNameCollection(dataToAssign), e);
3679     }
3680   }
3681 
3682   /**
3683    * null safe iterator getter if the type if collection
3684    *
3685    * @param collection
3686    * @return the iterator
3687    */
3688   public static Iterator iterator(Object collection) {
3689     if (collection == null) {
3690       return null;
3691     }
3692     // array list doesnt need an iterator
3693     if (collection instanceof Collection
3694         && !(collection instanceof ArrayList)) {
3695       return ((Collection) collection).iterator();
3696     }
3697     return null;
3698   }
3699 
3700   /**
3701    * Null safe array length or map
3702    *
3703    * @param arrayOrCollection
3704    * @return the length of the array (0 for null)
3705    */
3706   public static int length(Object arrayOrCollection) {
3707     if (arrayOrCollection == null) {
3708       return 0;
3709     }
3710     if (arrayOrCollection.getClass().isArray()) {
3711       return Array.getLength(arrayOrCollection);
3712     }
3713     if (arrayOrCollection instanceof Collection) {
3714       return ((Collection) arrayOrCollection).size();
3715     }
3716     if (arrayOrCollection instanceof Map) {
3717       return ((Map) arrayOrCollection).size();
3718     }
3719     // simple non array non collection object
3720     return 1;
3721   }
3722 
3723   /**
3724    * is array or collection
3725    *
3726    * @param arrayOrCollection
3727    * @return true or false
3728    */
3729   public static boolean isArrayOrCollection(Object arrayOrCollection) {
3730     if (arrayOrCollection == null) {
3731       return false;
3732     }
3733     return (arrayOrCollection.getClass().isArray() || arrayOrCollection instanceof Collection);
3734   }
3735 
3736   /**
3737    * If array, get the element based on index, if Collection, get it based on
3738    * iterator.
3739    *
3740    * @param arrayOrCollection
3741    * @param iterator
3742    * @param index
3743    * @return the object
3744    */
3745   public static Object next(Object arrayOrCollection, Iterator iterator,
3746       int index) {
3747     if (arrayOrCollection.getClass().isArray()) {
3748       return Array.get(arrayOrCollection, index);
3749     }
3750     if (arrayOrCollection instanceof ArrayList) {
3751       return ((ArrayList) arrayOrCollection).get(index);
3752     }
3753     if (arrayOrCollection instanceof Collection) {
3754       return iterator.next();
3755     }
3756     // simple object
3757     if (0 == index) {
3758       return arrayOrCollection;
3759     }
3760     throw new RuntimeException("Invalid class type: "
3761         + arrayOrCollection.getClass().getName());
3762   }
3763 
3764   /**
3765    * Remove the iterator or index
3766    *
3767    * @param arrayOrCollection
3768    * @param index
3769    * @return the object list or new array
3770    */
3771   public static Object remove(Object arrayOrCollection,
3772       int index) {
3773     return remove(arrayOrCollection, null, index);
3774   }
3775 
3776   /**
3777    * Remove the iterator or index
3778    *
3779    * @param arrayOrCollection
3780    * @param iterator
3781    * @param index
3782    * @return the object list or new array
3783    */
3784   public static Object remove(Object arrayOrCollection, Iterator iterator,
3785       int index) {
3786 
3787     //if theres an iterator, just use that
3788     if (iterator != null) {
3789       iterator.remove();
3790       return arrayOrCollection;
3791     }
3792     if (arrayOrCollection.getClass().isArray()) {
3793       int newLength = Array.getLength(arrayOrCollection) - 1;
3794       Object newArray = Array.newInstance(arrayOrCollection.getClass().getComponentType(), newLength);
3795       if (newLength == 0) {
3796         return newArray;
3797       }
3798       if (index > 0) {
3799         System.arraycopy(arrayOrCollection, 0, newArray, 0, index);
3800       }
3801       if (index < newLength) {
3802         System.arraycopy(arrayOrCollection, index+1, newArray, index, newLength - index);
3803       }
3804       return newArray;
3805     }
3806     if (arrayOrCollection instanceof List) {
3807       ((List)arrayOrCollection).remove(index);
3808       return arrayOrCollection;
3809     } else if (arrayOrCollection instanceof Collection) {
3810       //this should work unless there are duplicates or something weird
3811       ((Collection)arrayOrCollection).remove(get(arrayOrCollection, index));
3812       return arrayOrCollection;
3813     }
3814     throw new RuntimeException("Invalid class type: "
3815         + arrayOrCollection.getClass().getName());
3816   }
3817 
3818   /**
3819    * print the simple names of a list of classes
3820    * @param object
3821    * @return the simple names
3822    */
3823   public static String classesString(Object object) {
3824     StringBuilder result = new StringBuilder();
3825     if (object.getClass().isArray()) {
3826       int length = Array.getLength(object);
3827       for (int i=0;i<length;i++) {
3828         result.append(((Class)Array.get(object, i)).getSimpleName());
3829         if (i < length-1) {
3830           result.append(", ");
3831         }
3832       }
3833       return result.toString();
3834     }
3835 
3836     throw new RuntimeException("Not implemented: " + className(object));
3837   }
3838 
3839   /**
3840    * null safe classname method, max out at 20
3841    *
3842    * @param object
3843    * @return the classname
3844    */
3845   public static String classNameCollection(Object object) {
3846     if (object == null) {
3847       return null;
3848     }
3849     StringBuffer result = new StringBuffer();
3850 
3851     Iterator iterator = iterator(object);
3852     int length = length(object);
3853     for (int i = 0; i < length && i < 20; i++) {
3854       result.append(className(next(object, iterator, i)));
3855       if (i != length - 1) {
3856         result.append(", ");
3857       }
3858     }
3859     return result.toString();
3860   }
3861 
3862   /**
3863    * null safe classname method, gets the unenhanced name
3864    *
3865    * @param object
3866    * @return the classname
3867    */
3868   public static String className(Object object) {
3869     return object == null ? null : unenhanceClass(object.getClass())
3870         .getName();
3871   }
3872 
3873   /**
3874    * if a class is enhanced, get the unenhanced version
3875    *
3876    * @param theClass
3877    * @return the unenhanced version
3878    */
3879   public static Class unenhanceClass(Class theClass) {
3880     try {
3881 //this was cglib
3882 //      while (Enhancer.isEnhanced(theClass)) {
3883 //        theClass = theClass.getSuperclass();
3884 //      }
3885 
3886       while (ProxyObject.class.isAssignableFrom(theClass)) {
3887         theClass = theClass.getSuperclass();
3888       }
3889 
3890       return theClass;
3891     } catch (Exception e) {
3892       throw new RuntimeException("Problem unenhancing " + theClass, e);
3893     }
3894   }
3895 
3896   /**
3897    * assign data to a field
3898    *
3899    * @param field
3900    *            is the field to assign to
3901    * @param invokeOn
3902    *            to call on or null for static
3903    * @param dataToAssign
3904    *            data
3905    * @param overrideSecurity
3906    *            true to call on protected or private etc methods
3907    * @param typeCast
3908    *            true if we should typecast
3909    * @param annotationWithValueOverride
3910    *            annotation with value of override, or null if none
3911    */
3912   @SuppressWarnings("unchecked")
3913   public static void assignField(Field field, Object invokeOn,
3914       Object dataToAssign, boolean overrideSecurity, boolean typeCast,
3915       Class<? extends Annotation> annotationWithValueOverride) {
3916 
3917     if (annotationWithValueOverride != null) {
3918       // see if in annotation
3919       Annotation annotation = field
3920           .getAnnotation(annotationWithValueOverride);
3921       if (annotation != null) {
3922 
3923          // type of the value, or String if not specific Class
3924           // typeOfAnnotationValue = typeCast ? field.getType() :
3925           // String.class; dataToAssign =
3926           // AnnotationUtils.retrieveAnnotationValue(
3927           // typeOfAnnotationValue, annotation, "value");
3928 
3929         throw new RuntimeException("Not supported");
3930       }
3931     }
3932     assignField(field, invokeOn, dataToAssign, overrideSecurity, typeCast);
3933   }
3934 
3935   /**
3936    * assign data to a field. Will find the field in superclasses, will
3937    * typecast, and will override security (private, protected, etc)
3938    *
3939    * @param invokeOn
3940    *            to call on or null for static
3941    * @param fieldName
3942    *            method name to call
3943    * @param dataToAssign
3944    *            data
3945    */
3946   public static void assignField(Object invokeOn, String fieldName,
3947       Object dataToAssign) {
3948     assignField(null, invokeOn, fieldName, dataToAssign, true, true, true,
3949         null);
3950   }
3951 
3952   /**
3953    * get a field object for a class, potentially in superclasses
3954    *
3955    * @param theClass
3956    * @param fieldName
3957    * @param callOnSupers
3958    *            true if superclasses should be looked in for the field
3959    * @param throwExceptionIfNotFound
3960    *            will throw runtime exception if not found
3961    * @return the field object or null if not found (or exception if param is
3962    *         set)
3963    */
3964   public static Field field(Class theClass, String fieldName,
3965       boolean callOnSupers, boolean throwExceptionIfNotFound) {
3966     try {
3967       Field field = theClass.getDeclaredField(fieldName);
3968       // found it
3969       return field;
3970     } catch (NoSuchFieldException e) {
3971       // if method not found
3972       // if traversing up, and not Object, and not instance method
3973       if (callOnSupers && !theClass.equals(Object.class)) {
3974         return field(theClass.getSuperclass(), fieldName, callOnSupers,
3975             throwExceptionIfNotFound);
3976       }
3977     }
3978     // maybe throw an exception
3979     if (throwExceptionIfNotFound) {
3980       throw new RuntimeException("Cant find field: " + fieldName
3981           + ", in: " + theClass + ", callOnSupers: " + callOnSupers);
3982     }
3983     return null;
3984   }
3985 
3986   /**
3987    * return a set of Strings for a class and type. This is not for any
3988    * supertypes, only for the type at hand. includes final fields
3989    *
3990    * @param theClass
3991    * @param fieldType
3992    *            or null for all
3993    * @param includeStaticFields
3994    * @return the set of strings, or the empty Set if none
3995    */
3996   @SuppressWarnings("unchecked")
3997   public static Set<String> fieldNames(Class theClass, Class fieldType,
3998       boolean includeStaticFields) {
3999     return fieldNamesHelper(theClass, theClass, fieldType, true, true,
4000         includeStaticFields, null, true);
4001   }
4002 
4003   /**
4004    * put in english how an object changed
4005    * @param theOld
4006    * @param theNew
4007    * @param differentFieldNames
4008    * @return the differences
4009    */
4010   public static String dbVersionDescribeDifferences(Object theOld, Object theNew, Set<String> differentFieldNames) {
4011 
4012     StringBuilder result = new StringBuilder(theOld == null ? "Old state unknown, new state is: " : "Fields changed: ");
4013 
4014     int length = length(differentFieldNames);
4015 
4016     if (length == 0) {
4017       result.append("none");
4018       return result.toString();
4019     }
4020     int i=0;
4021     for (String fieldName : nonNull(differentFieldNames)) {
4022       result.append(fieldName);
4023       if (i < length-1) {
4024         result.append(", ");
4025       } else {
4026         result.append(".\n");
4027       }
4028       i++;
4029     }
4030     //do each field
4031     i=0;
4032     for (String fieldName : nonNull(differentFieldNames)) {
4033       String oldString = theOld == null ? "?" : stringValue(fieldValue(theOld, fieldName));
4034       oldString = StringUtils.abbreviate(oldString, 200);
4035       String newString = stringValue(fieldValue(theNew, fieldName));
4036       newString = StringUtils.abbreviate(newString, 200);
4037       result.append(fieldName).append(": FROM: '").append(oldString).append("', TO: '").append(newString).append("'");
4038       if (i < length-1) {
4039         result.append("\n");
4040       }
4041       i++;
4042     }
4043     return result.toString();
4044   }
4045 
4046   /**
4047    * get all field names from a class, including superclasses (if specified)
4048    *
4049    * @param theClass
4050    *            to look for fields in
4051    * @param superclassToStopAt
4052    *            to go up to or null to go up to Object
4053    * @param fieldType
4054    *            is the type of the field to get
4055    * @param includeSuperclassToStopAt
4056    *            if we should include the superclass
4057    * @param includeStaticFields
4058    *            if include static fields
4059    * @param includeFinalFields
4060    *            if final fields should be included
4061    * @return the set of field names or empty set if none
4062    */
4063   public static Set<String> fieldNames(Class theClass,
4064       Class superclassToStopAt, Class<?> fieldType,
4065       boolean includeSuperclassToStopAt, boolean includeStaticFields,
4066       boolean includeFinalFields) {
4067     return fieldNamesHelper(theClass, superclassToStopAt, fieldType,
4068         includeSuperclassToStopAt, includeStaticFields,
4069         includeFinalFields, null, true);
4070 
4071   }
4072 
4073   /**
4074    * get all field names from a class, including superclasses (if specified).
4075    * ignore a certain marker annotation
4076    *
4077    * @param theClass
4078    *            to look for fields in
4079    * @param superclassToStopAt
4080    *            to go up to or null to go up to Object
4081    * @param fieldType
4082    *            is the type of the field to get
4083    * @param includeSuperclassToStopAt
4084    *            if we should include the superclass
4085    * @param includeStaticFields
4086    *            if include static fields
4087    * @param includeFinalFields
4088    *            if final fields should be included
4089    * @param markerAnnotationToIngore
4090    *            if this is not null, then if the field has this annotation,
4091    *            then do not include in list
4092    * @return the set of field names
4093    */
4094   public static Set<String> fieldNames(Class theClass,
4095       Class superclassToStopAt, Class<?> fieldType,
4096       boolean includeSuperclassToStopAt, boolean includeStaticFields,
4097       boolean includeFinalFields,
4098       Class<? extends Annotation> markerAnnotationToIngore) {
4099     return fieldNamesHelper(theClass, superclassToStopAt, fieldType,
4100         includeSuperclassToStopAt, includeStaticFields,
4101         includeFinalFields, markerAnnotationToIngore, false);
4102 
4103   }
4104 
4105   /**
4106    * get all field names from a class, including superclasses (if specified)
4107    * (up to and including the specified superclass). ignore a certain marker
4108    * annotation. Dont get static or final field, and get fields of all types
4109    *
4110    * @param theClass
4111    *            to look for fields in
4112    * @param superclassToStopAt
4113    *            to go up to or null to go up to Object
4114    * @param markerAnnotationToIngore
4115    *            if this is not null, then if the field has this annotation,
4116    *            then do not include in list
4117    * @return the set of field names or empty set if none
4118    */
4119   public static Set<String> fieldNames(Class theClass,
4120       Class superclassToStopAt,
4121       Class<? extends Annotation> markerAnnotationToIngore) {
4122     return fieldNamesHelper(theClass, superclassToStopAt, null, true,
4123         false, false, markerAnnotationToIngore, false);
4124   }
4125 
4126   /**
4127    * get all field names from a class, including superclasses (if specified)
4128    *
4129    * @param theClass
4130    *            to look for fields in
4131    * @param superclassToStopAt
4132    *            to go up to or null to go up to Object
4133    * @param fieldType
4134    *            is the type of the field to get
4135    * @param includeSuperclassToStopAt
4136    *            if we should include the superclass
4137    * @param includeStaticFields
4138    *            if include static fields
4139    * @param includeFinalFields
4140    *            true to include finals
4141    * @param markerAnnotation
4142    *            if this is not null, then if the field has this annotation,
4143    *            then do not include in list (if includeAnnotation is false)
4144    * @param includeAnnotation
4145    *            true if the attribute should be included if annotation is
4146    *            present, false if exclude
4147    * @return the set of field names or empty set if none
4148    */
4149   @SuppressWarnings("unchecked")
4150   static Set<String> fieldNamesHelper(Class theClass,
4151       Class superclassToStopAt, Class<?> fieldType,
4152       boolean includeSuperclassToStopAt, boolean includeStaticFields,
4153       boolean includeFinalFields,
4154       Class<? extends Annotation> markerAnnotation,
4155       boolean includeAnnotation) {
4156     Set<Field> fieldSet = fieldsHelper(theClass, superclassToStopAt,
4157         fieldType, includeSuperclassToStopAt, includeStaticFields,
4158         includeFinalFields, markerAnnotation, includeAnnotation);
4159     Set<String> fieldNameSet = new LinkedHashSet<String>();
4160     for (Field field : fieldSet) {
4161       fieldNameSet.add(field.getName());
4162     }
4163     return fieldNameSet;
4164 
4165   }
4166 
4167   /**
4168    * get all fields from a class, including superclasses (if specified)
4169    *
4170    * @param theClass
4171    *            to look for fields in
4172    * @param superclassToStopAt
4173    *            to go up to or null to go up to Object
4174    * @param fieldType
4175    *            is the type of the field to get
4176    * @param includeSuperclassToStopAt
4177    *            if we should include the superclass
4178    * @param includeStaticFields
4179    *            if include static fields
4180    * @param includeFinalFields
4181    *            if final fields should be included
4182    * @param markerAnnotation
4183    *            if this is not null, then if the field has this annotation,
4184    *            then do not include in list (if includeAnnotation is false)
4185    * @param includeAnnotation
4186    *            true if the attribute should be included if annotation is
4187    *            present, false if exclude
4188    * @return the set of fields (wont return null)
4189    */
4190   @SuppressWarnings("unchecked")
4191   public static Set<Field> fields(Class theClass, Class superclassToStopAt,
4192       Class fieldType, boolean includeSuperclassToStopAt,
4193       boolean includeStaticFields, boolean includeFinalFields,
4194       Class<? extends Annotation> markerAnnotation,
4195       boolean includeAnnotation) {
4196     return fieldsHelper(theClass, superclassToStopAt, fieldType,
4197         includeSuperclassToStopAt, includeStaticFields,
4198         includeFinalFields, markerAnnotation, includeAnnotation);
4199   }
4200 
4201   /**
4202    * get all fields from a class, including superclasses (if specified) (up to
4203    * and including the specified superclass). ignore a certain marker
4204    * annotation, or only include it. Dont get static or final field, and get
4205    * fields of all types
4206    *
4207    * @param theClass
4208    *            to look for fields in
4209    * @param superclassToStopAt
4210    *            to go up to or null to go up to Object
4211    * @param markerAnnotation
4212    *            if this is not null, then if the field has this annotation,
4213    *            then do not include in list (if includeAnnotation is false)
4214    * @param includeAnnotation
4215    *            true if the attribute should be included if annotation is
4216    *            present, false if exclude
4217    * @return the set of field names or empty set if none
4218    */
4219   @SuppressWarnings("unchecked")
4220   public static Set<Field> fields(Class theClass, Class superclassToStopAt,
4221       Class<? extends Annotation> markerAnnotation,
4222       boolean includeAnnotation) {
4223     return fieldsHelper(theClass, superclassToStopAt, null, true, false,
4224         false, markerAnnotation, includeAnnotation);
4225   }
4226 
4227   /**
4228    * get all fields from a class, including superclasses (if specified)
4229    *
4230    * @param theClass
4231    *            to look for fields in
4232    * @param superclassToStopAt
4233    *            to go up to or null to go up to Object
4234    * @param fieldType
4235    *            is the type of the field to get
4236    * @param includeSuperclassToStopAt
4237    *            if we should include the superclass
4238    * @param includeStaticFields
4239    *            if include static fields
4240    * @param includeFinalFields
4241    *            if final fields should be included
4242    * @param markerAnnotation
4243    *            if this is not null, then if the field has this annotation,
4244    *            then do not include in list (if includeAnnotation is false)
4245    * @param includeAnnotation
4246    *            true if the attribute should be included if annotation is
4247    *            present, false if exclude
4248    * @return the set of fields (wont return null)
4249    */
4250   @SuppressWarnings("unchecked")
4251   static Set<Field> fieldsHelper(Class theClass, Class superclassToStopAt,
4252       Class<?> fieldType, boolean includeSuperclassToStopAt,
4253       boolean includeStaticFields, boolean includeFinalFields,
4254       Class<? extends Annotation> markerAnnotation,
4255       boolean includeAnnotation) {
4256     // MAKE SURE IF ANY MORE PARAMS ARE ADDED, THE CACHE KEY IS CHANGED!
4257 
4258     Set<Field> fieldNameSet = null;
4259     String cacheKey = theClass + CACHE_SEPARATOR + superclassToStopAt
4260         + CACHE_SEPARATOR + fieldType + CACHE_SEPARATOR
4261         + includeSuperclassToStopAt + CACHE_SEPARATOR
4262         + includeStaticFields + CACHE_SEPARATOR + includeFinalFields
4263         + CACHE_SEPARATOR + markerAnnotation + CACHE_SEPARATOR
4264         + includeAnnotation;
4265     fieldNameSet = fieldSetCache().get(cacheKey);
4266     if (fieldNameSet != null) {
4267       return fieldNameSet;
4268     }
4269 
4270     fieldNameSet = new LinkedHashSet<Field>();
4271     fieldsHelper(theClass, superclassToStopAt, fieldType,
4272         includeSuperclassToStopAt, includeStaticFields,
4273         includeFinalFields, markerAnnotation, fieldNameSet,
4274         includeAnnotation);
4275 
4276     // add to cache
4277     fieldSetCache().put(cacheKey, fieldNameSet);
4278 
4279     return fieldNameSet;
4280 
4281   }
4282 
4283   /**
4284    * compare two objects, compare primitives, Strings, maps of string attributes.
4285    * if both objects equal each others references, then return empty set.
4286    * then, if not, then if either is null, return all fields
4287    * @param first
4288    * @param second
4289    * @param fieldsToCompare
4290    * @param mapPrefix is the prefix for maps which are compared (e.g. attribute__)
4291    * @return the set of fields which are different.  never returns null
4292    */
4293   public static Set<String> compareObjectFields(Object first, Object second,
4294       Set<String> fieldsToCompare, String mapPrefix) {
4295 
4296     Set<String> differentFields = new LinkedHashSet<String>();
4297 
4298     if (first == second) {
4299       return differentFields;
4300     }
4301 
4302     //if either null, then all fields are different
4303     if (first == null || second == null) {
4304       differentFields.addAll(fieldsToCompare);
4305     }
4306 
4307     for (String fieldName : fieldsToCompare) {
4308       try {
4309         Object firstValue = fieldValue(first, fieldName);
4310         Object secondValue = fieldValue(second, fieldName);
4311 
4312         if (firstValue == secondValue) {
4313           continue;
4314         }
4315         if (firstValue instanceof Map || secondValue instanceof Map) {
4316           mapDifferences((Map)firstValue, (Map)secondValue, differentFields, mapPrefix);
4317           continue;
4318         }
4319         //compare things...
4320         //for strings, null is equal to empty
4321         if (firstValue instanceof String || secondValue instanceof String) {
4322           if (!equals(defaultString((String)firstValue),
4323               defaultString((String)secondValue))) {
4324             differentFields.add(fieldName);
4325           }
4326           continue;
4327         }
4328         //if one is null, that is not good
4329         if (firstValue == null || secondValue == null) {
4330           differentFields.add(fieldName);
4331           continue;
4332         }
4333         //everything (numbers, dates, etc) should work with equals method...
4334         if (!firstValue.equals(secondValue)) {
4335           differentFields.add(fieldName);
4336           continue;
4337         }
4338 
4339       } catch (RuntimeException re) {
4340         throw new RuntimeException("Problem comparing field " + fieldName
4341             + " on objects: " + className(first) + ", " + className(second));
4342       }
4343 
4344 
4345     }
4346     return differentFields;
4347   }
4348 
4349   /**
4350    * clone an object, assign primitives, Strings, maps of string attributes.  Clone GrouperCloneable fields.
4351    * @param <T> template
4352    * @param object
4353    * @param fieldsToClone
4354    * @return the cloned object or null if input is null
4355    */
4356   public static <T> T clone(T object, Set<String> fieldsToClone) {
4357 
4358     //make a return object
4359     T result = (T)newInstance(object.getClass());
4360 
4361     cloneFields(object, result, fieldsToClone);
4362 
4363     return result;
4364   }
4365 
4366   /**
4367    * clone an object, assign primitives, Strings, maps of string attributes.  Clone GrouperCloneable fields.
4368    * @param <T> template
4369    * @param object
4370    * @param result
4371    * @param fieldsToClone
4372    */
4373   public static <T> void cloneFields(T object, T result,
4374       Set<String> fieldsToClone) {
4375 
4376     if (object == result) {
4377       return;
4378     }
4379 
4380     //if either null, then all fields are different
4381     if (object == null || result == null) {
4382       throw new RuntimeException("Cant copy from or to null: " + className(object) + ", " + className(result));
4383     }
4384 
4385     Class<?> fieldValueClass = null;
4386 
4387     for (String fieldName : nonNull(fieldsToClone)) {
4388       try {
4389 
4390         Object fieldValue = fieldValue(object, fieldName);
4391         fieldValueClass = fieldValue == null ? null : fieldValue.getClass();
4392 
4393         Object fieldValueToAssign = cloneValue(fieldValue);
4394 
4395         //assign the field to the clone
4396         assignField(result, fieldName, fieldValueToAssign);
4397 
4398       } catch (RuntimeException re) {
4399         throw new RuntimeException("Problem cloning field: " + object.getClass()
4400               + ", " + fieldName + ", " + fieldValueClass, re);
4401       }
4402     }
4403   }
4404 
4405   /**
4406    * helper method to clone the value of a field.  just returns the same
4407    * reference for primitives and immutables.  Will subclone GrouperCloneables,
4408    * and will throw exception if not expecting the type.  Will clone sets, lists, maps.
4409    * @param <T> template
4410    *
4411    * @param value
4412    * @return the cloned value
4413    */
4414   public static <T> T cloneValue(T value) {
4415 
4416     Object clonedValue = value;
4417 
4418     if (value == null || value instanceof String || value instanceof Class<?>
4419         || value instanceof Enum<?>
4420         || value.getClass().isPrimitive() || value instanceof Number
4421         || value instanceof Boolean
4422         || value instanceof Date || value instanceof Configuration
4423         || value instanceof Subject || value.getClass().isEnum()) {
4424       //clone things
4425       //for strings, and immutable classes, just assign
4426       //nothing to do, just assign the value
4427     } else if (value instanceof GrouperCloneable) {
4428 
4429       //lets clone the object
4430       clonedValue = ((GrouperCloneable)value).clone();
4431 
4432     } else if (value instanceof Map) {
4433       clonedValue = new LinkedHashMap();
4434       Map mapValue = (Map)value;
4435       Map clonedMapValue = (Map)clonedValue;
4436       for (Object key : mapValue.keySet()) {
4437         clonedMapValue.put(cloneValue(key), cloneValue(mapValue.get(key)));
4438       }
4439     } else if (value instanceof Set) {
4440         clonedValue = new LinkedHashSet();
4441         Set setValue = (Set)value;
4442         Set clonedSetValue = (Set)clonedValue;
4443         for (Object each : setValue) {
4444           clonedSetValue.add(cloneValue(each));
4445         }
4446     } else if (value instanceof List) {
4447       clonedValue = new ArrayList();
4448       List listValue = (List)value;
4449       List clonedListValue = (List)clonedValue;
4450       for (Object each : listValue) {
4451         clonedListValue.add(cloneValue(each));
4452       }
4453     } else if (value.getClass().isArray()) {
4454       clonedValue = Array.newInstance(value.getClass().getComponentType(), Array.getLength(value));
4455       for (int i=0;i<Array.getLength(value);i++) {
4456         Array.set(clonedValue, i, cloneValue(Array.get(value, i)));
4457       }
4458 
4459     } else {
4460 
4461       //this means lets add support for a new type of object
4462       throw new RuntimeException("Unexpected class in clone method: " + value.getClass());
4463 
4464     }
4465     return (T)clonedValue;
4466   }
4467 
4468   /**
4469    * simple method to get method names
4470    * @param theClass
4471    * @param superclassToStopAt
4472    * @param includeSuperclassToStopAt
4473    * @param includeStaticMethods
4474    * @return the set of method names
4475    */
4476   public static Set<String> methodNames(Class<?> theClass, Class<?> superclassToStopAt,
4477       boolean includeSuperclassToStopAt, boolean includeStaticMethods) {
4478 
4479     Set<Method> methods = new LinkedHashSet<Method>();
4480     methodsHelper(theClass, superclassToStopAt, includeSuperclassToStopAt, includeStaticMethods,
4481         null, false, methods);
4482     Set<String> methodNames = new HashSet<String>();
4483     for (Method method : methods) {
4484       methodNames.add(method.getName());
4485     }
4486     return methodNames;
4487   }
4488 
4489   /**
4490    * simple method to get method names
4491    * @param theClass
4492    * @param methodName
4493    * @param superclassToStopAt
4494    * @param includeSuperclassToStopAt
4495    * @param includeStaticMethods
4496    * @param exceptionIfNotFound
4497    * @return the set of method names
4498    */
4499   public static Method methodByName(Class<?> theClass, String methodName, Class<?> superclassToStopAt,
4500       boolean includeSuperclassToStopAt, boolean includeStaticMethods, boolean exceptionIfNotFound) {
4501 
4502     Set<Method> methods = new LinkedHashSet<Method>();
4503     methodsByNameHelper(theClass, methodName, superclassToStopAt,
4504         includeSuperclassToStopAt, includeStaticMethods,
4505         null, false, methods);
4506     if (methods.size() > 1) {
4507       throw new RuntimeException("There are more than one method with name " + methodName + " in class: " + theClass);
4508     }
4509 
4510     if (methods.size() == 1) {
4511       return methods.iterator().next();
4512     }
4513 
4514     if (exceptionIfNotFound) {
4515       throw new RuntimeException("Could not find method " + methodName + " in class: " + theClass);
4516     }
4517 
4518     return null;
4519   }
4520 
4521   /**
4522    * get the set of methods
4523    * @param theClass
4524    * @param superclassToStopAt
4525    * @param includeSuperclassToStopAt
4526    * @param includeStaticMethods
4527    * @param markerAnnotation
4528    * @param includeAnnotation
4529    * @param methodSet
4530    */
4531   public static void methodsHelper(Class<?> theClass, Class<?> superclassToStopAt,
4532       boolean includeSuperclassToStopAt,
4533       boolean includeStaticMethods, Class<? extends Annotation> markerAnnotation,
4534       boolean includeAnnotation, Set<Method> methodSet) {
4535     theClass = unenhanceClass(theClass);
4536     Method[] methods = theClass.getDeclaredMethods();
4537     if (length(methods) != 0) {
4538       for (Method method : methods) {
4539         // if not static, then continue
4540         if (!includeStaticMethods
4541             && Modifier.isStatic(method.getModifiers())) {
4542           continue;
4543         }
4544         // if checking for annotation
4545         if (markerAnnotation != null
4546             && (includeAnnotation != method
4547                 .isAnnotationPresent(markerAnnotation))) {
4548           continue;
4549         }
4550         // go for it
4551         methodSet.add(method);
4552       }
4553     }
4554     // see if done recursing (if superclassToStopAt is null, then stop at
4555     // Object
4556     if (theClass.equals(superclassToStopAt)
4557         || theClass.equals(Object.class)) {
4558       return;
4559     }
4560     Class superclass = theClass.getSuperclass();
4561     if (!includeSuperclassToStopAt && superclass.equals(superclassToStopAt)) {
4562       return;
4563     }
4564     // recurse
4565     methodsHelper(superclass, superclassToStopAt,
4566         includeSuperclassToStopAt, includeStaticMethods,
4567         markerAnnotation, includeAnnotation, methodSet);
4568 
4569   }
4570 
4571   /**
4572    * get the set of methods
4573    * @param theClass
4574    * @param methodName
4575    * @param paramTypesOrArrayOrList
4576    *            types of the params
4577    * @param superclassToStopAt
4578    * @param includeSuperclassToStopAt
4579    * @param isStaticOrInstance true if static
4580    * @param markerAnnotation
4581    * @param includeAnnotation
4582    * @return the method or null if not found
4583    *
4584    */
4585   public static Method method(Class<?> theClass,
4586       String methodName, Object paramTypesOrArrayOrList,
4587       Class<?> superclassToStopAt,
4588       boolean includeSuperclassToStopAt,
4589       boolean isStaticOrInstance, Class<? extends Annotation> markerAnnotation,
4590       boolean includeAnnotation) {
4591     theClass = unenhanceClass(theClass);
4592 
4593     Class[] paramTypesArray = (Class[]) toArray(paramTypesOrArrayOrList);
4594 
4595     Method method = null;
4596 
4597     try {
4598       method = theClass.getDeclaredMethod(methodName, paramTypesArray);
4599     } catch (NoSuchMethodException nsme) {
4600       //this is ok
4601     } catch (Exception e) {
4602       throw new RuntimeException("Problem retrieving method: " + theClass.getSimpleName() + ", " + methodName, e);
4603     }
4604 
4605     if (method != null) {
4606       //we found a method, make sure it is valid
4607       // if not static, then return null (dont worry about superclass)
4608       if (!isStaticOrInstance
4609           && Modifier.isStatic(method.getModifiers())) {
4610         return null;
4611       }
4612       // if checking for annotation, if not there, then recurse
4613       if (markerAnnotation == null
4614           || (includeAnnotation == method
4615               .isAnnotationPresent(markerAnnotation))) {
4616         return method;
4617       }
4618     }
4619     // see if done recursing (if superclassToStopAt is null, then stop at
4620     // Object
4621     if (theClass.equals(superclassToStopAt)
4622         || theClass.equals(Object.class)) {
4623       return null;
4624     }
4625     Class superclass = theClass.getSuperclass();
4626     if (!includeSuperclassToStopAt && superclass.equals(superclassToStopAt)) {
4627       return null;
4628     }
4629     // recurse
4630     return method(superclass, methodName, paramTypesArray, superclassToStopAt,
4631         includeSuperclassToStopAt, isStaticOrInstance, markerAnnotation, includeAnnotation);
4632   }
4633 
4634   /**
4635    * get all field names from a class, including superclasses (if specified)
4636    *
4637    * @param theClass
4638    *            to look for fields in
4639    * @param superclassToStopAt
4640    *            to go up to or null to go up to Object
4641    * @param fieldType
4642    *            is the type of the field to get
4643    * @param includeSuperclassToStopAt
4644    *            if we should include the superclass
4645    * @param includeStaticFields
4646    *            if include static fields
4647    * @param includeFinalFields
4648    *            if final fields should be included
4649    * @param markerAnnotation
4650    *            if this is not null, then if the field has this annotation,
4651    *            then do not include in list
4652    * @param fieldSet
4653    *            set to add fields to
4654    * @param includeAnnotation
4655    *            if include or exclude
4656    */
4657   @SuppressWarnings("unchecked")
4658   private static void fieldsHelper(Class theClass, Class superclassToStopAt,
4659       Class<?> fieldType, boolean includeSuperclassToStopAt,
4660       boolean includeStaticFields, boolean includeFinalFields,
4661       Class<? extends Annotation> markerAnnotation, Set<Field> fieldSet,
4662       boolean includeAnnotation) {
4663     theClass = unenhanceClass(theClass);
4664     Field[] fields = theClass.getDeclaredFields();
4665     if (length(fields) != 0) {
4666       for (Field field : fields) {
4667         // if checking for type, and not right type, continue
4668         if (fieldType != null
4669             && !fieldType.isAssignableFrom(field.getType())) {
4670           continue;
4671         }
4672         // if not static, then continue
4673         if (!includeStaticFields
4674             && Modifier.isStatic(field.getModifiers())) {
4675           continue;
4676         }
4677         // if not final constinue
4678         if (!includeFinalFields
4679             && Modifier.isFinal(field.getModifiers())) {
4680           continue;
4681         }
4682         // if checking for annotation
4683         if (markerAnnotation != null
4684             && (includeAnnotation != field
4685                 .isAnnotationPresent(markerAnnotation))) {
4686           continue;
4687         }
4688         // go for it
4689         fieldSet.add(field);
4690       }
4691     }
4692     // see if done recursing (if superclassToStopAt is null, then stop at
4693     // Object
4694     if (theClass.equals(superclassToStopAt)
4695         || theClass.equals(Object.class)) {
4696       return;
4697     }
4698     Class superclass = theClass.getSuperclass();
4699     if (!includeSuperclassToStopAt && superclass.equals(superclassToStopAt)) {
4700       return;
4701     }
4702     // recurse
4703     fieldsHelper(superclass, superclassToStopAt, fieldType,
4704         includeSuperclassToStopAt, includeStaticFields,
4705         includeFinalFields, markerAnnotation, fieldSet,
4706         includeAnnotation);
4707   }
4708 
4709   /**
4710    * find out a field value
4711    *
4712    * @param theClass
4713    *            the class which has the method
4714    * @param invokeOn
4715    *            to call on or null for static
4716    * @param fieldName
4717    *            method name to call
4718    * @param callOnSupers
4719    *            if static and method not exists, try on supers
4720    * @param overrideSecurity
4721    *            true to call on protected or private etc methods
4722    * @param considerFieldValuable if the FieldValueable interface should be considered
4723    * @return the current value
4724    */
4725   public static Object fieldValue(Class theClass, Object invokeOn,
4726       String fieldName, boolean callOnSupers, boolean overrideSecurity, boolean considerFieldValuable) {
4727 
4728     //if it has the interface to customize
4729     if (considerFieldValuable && (invokeOn instanceof FieldValuable)) {
4730       return ((FieldValuable)invokeOn).fieldValue(fieldName);
4731     }
4732 
4733     Field field = null;
4734 
4735     // only if the method exists, try to execute
4736     try {
4737       // ok if null
4738       if (theClass == null) {
4739         theClass = invokeOn.getClass();
4740       }
4741       field = field(theClass, fieldName, callOnSupers, true);
4742       return fieldValue(field, invokeOn, overrideSecurity);
4743     } catch (Exception e) {
4744       throw new RuntimeException("Cant execute reflection field: "
4745           + fieldName + ", on: " + className(invokeOn), e);
4746     }
4747   }
4748 
4749   /**
4750    * has fieldValue method
4751    */
4752   public static interface FieldValuable {
4753     /**
4754      * call this method to get the field value (e.g. from dbVersionDifferentFields).
4755      * some objects have different interpretations (e.g. Group will process attribute__whatever)
4756      * @param fieldName
4757      * @return the value
4758      */
4759     public Object fieldValue(String fieldName);
4760 
4761   }
4762 
4763   /**
4764    * get the value of a field, override security if needbe
4765    *
4766    * @param field
4767    * @param invokeOn
4768    * @return the value of the field
4769    */
4770   public static Object fieldValue(Field field, Object invokeOn) {
4771     return fieldValue(field, invokeOn, true);
4772   }
4773 
4774   /**
4775    * get the value of a field
4776    *
4777    * @param field
4778    * @param invokeOn
4779    * @param overrideSecurity
4780    * @return the value of the field
4781    */
4782   public static Object fieldValue(Field field, Object invokeOn,
4783       boolean overrideSecurity) {
4784 
4785     if (overrideSecurity) {
4786       field.setAccessible(true);
4787     }
4788     try {
4789       return field.get(invokeOn);
4790     } catch (Exception e) {
4791       throw new RuntimeException("Cant execute reflection field: "
4792           + field.getName() + ", on: " + className(invokeOn), e);
4793 
4794     }
4795 
4796   }
4797 
4798   /**
4799    * find out a field value (invoke on supers, override security)
4800    *
4801    * @param invokeOn
4802    *            to call on or null for static
4803    * @param fieldName
4804    *            method name to call
4805    * @return the current value
4806    */
4807   public static Object fieldValue(Object invokeOn, String fieldName) {
4808     return fieldValue(null, invokeOn, fieldName, true, true, true);
4809   }
4810 
4811   /**
4812    * get the decalred methods for a class, perhaps from cache
4813    *
4814    * @param theClass
4815    * @return the declared methods
4816    */
4817   private static Method[] retrieveDeclaredMethods(Class theClass) {
4818     Method[] methods = declaredMethodsCache().get(theClass);
4819     // get from cache if we can
4820     if (methods == null) {
4821       methods = theClass.getDeclaredMethods();
4822       declaredMethodsCache().put(theClass, methods);
4823     }
4824     return methods;
4825   }
4826 
4827   /**
4828    * helper method for calling a method with no params (could be in
4829    * superclass)
4830    *
4831    * @param theClass
4832    *            the class which has the method
4833    * @param invokeOn
4834    *            to call on or null for static
4835    * @param methodName
4836    *            method name to call
4837    * @return the data
4838    */
4839   public static Object callMethod(Class theClass, Object invokeOn,
4840       String methodName) {
4841     return callMethod(theClass, invokeOn, methodName, null, null);
4842   }
4843 
4844   /**
4845    * helper method for calling a method (could be in superclass)
4846    *
4847    * @param theClass
4848    *            the class which has the method
4849    * @param invokeOn
4850    *            to call on or null for static
4851    * @param methodName
4852    *            method name to call
4853    * @param paramTypesOrArrayOrList
4854    *            types of the params
4855    * @param paramsOrListOrArray
4856    *            data
4857    * @return the data
4858    */
4859   public static Object callMethod(Class theClass, Object invokeOn,
4860       String methodName, Object paramTypesOrArrayOrList,
4861       Object paramsOrListOrArray) {
4862     return callMethod(theClass, invokeOn, methodName,
4863         paramTypesOrArrayOrList, paramsOrListOrArray, true);
4864   }
4865 
4866   /**
4867    * helper method for calling a method
4868    *
4869    * @param theClass
4870    *            the class which has the method
4871    * @param invokeOn
4872    *            to call on or null for static
4873    * @param methodName
4874    *            method name to call
4875    * @param paramTypesOrArrayOrList
4876    *            types of the params
4877    * @param paramsOrListOrArray
4878    *            data
4879    * @param callOnSupers
4880    *            if static and method not exists, try on supers
4881    * @return the data
4882    */
4883   public static Object callMethod(Class theClass, Object invokeOn,
4884       String methodName, Object paramTypesOrArrayOrList,
4885       Object paramsOrListOrArray, boolean callOnSupers) {
4886     return callMethod(theClass, invokeOn, methodName,
4887         paramTypesOrArrayOrList, paramsOrListOrArray, callOnSupers,
4888         false);
4889   }
4890 
4891   /**
4892    * construct an instance by reflection
4893    * @param <T>
4894    * @param theClass
4895    * @param args
4896    * @param types
4897    * @return the instance
4898    */
4899   public static <T> T construct(Class<T> theClass, Class[] types, Object[] args) {
4900     try {
4901       Constructor<T> constructor = theClass.getConstructor(types);
4902 
4903       return constructor.newInstance(args);
4904 
4905     } catch (Exception e) {
4906       throw new RuntimeException("Having trouble with constructor for class: " + theClass.getSimpleName()
4907           + " and args: " + classesString(types), e);
4908      }
4909   }
4910 
4911   /**
4912    * <pre>
4913    * turn a list into map
4914    * </pre>
4915    * @param list is the list to convert
4916    * @param valueClass type of the result (can typecast)
4917    * @param keyClass is the type of the key of the map
4918    * @param <K> is the template of the key of the map
4919    * @param <V> is the template of the value of the map
4920    * @param keyPropertyName name of the javabeans property for the key in the map
4921    * @return the ordered set or the empty set if not found (never null)
4922    */
4923   public static <K, V> Map<K, V> listToMap(List<V> list, @SuppressWarnings("unused") final Class<K> keyClass,
4924       @SuppressWarnings("unused") final Class<V> valueClass, String keyPropertyName)  {
4925     Map<K,V> result = new LinkedHashMap<K, V>();
4926     for (V value : nonNull(list)) {
4927       K key = (K)propertyValue(value, keyPropertyName);
4928       result.put(key, value);
4929     }
4930     return result;
4931   }
4932 
4933   /**
4934    * helper method for calling a method
4935    *
4936    * @param theClass
4937    *            the class which has the method
4938    * @param invokeOn
4939    *            to call on or null for static
4940    * @param methodName
4941    *            method name to call
4942    * @param paramTypesOrArrayOrList
4943    *            types of the params
4944    * @param paramsOrListOrArray
4945    *            data
4946    * @param callOnSupers
4947    *            if static and method not exists, try on supers
4948    * @param overrideSecurity
4949    *            true to call on protected or private etc methods
4950    * @return the data
4951    */
4952   public static Object callMethod(Class theClass, Object invokeOn,
4953       String methodName, Object paramTypesOrArrayOrList,
4954       Object paramsOrListOrArray, boolean callOnSupers,
4955       boolean overrideSecurity) {
4956     try {
4957       Method method = null;
4958 
4959       Class[] paramTypesArray = (Class[]) toArray(paramTypesOrArrayOrList);
4960 
4961       try {
4962         method = theClass.getDeclaredMethod(methodName, paramTypesArray);
4963         if (overrideSecurity) {
4964           method.setAccessible(true);
4965         }
4966       } catch (Exception e) {
4967         // if method not found
4968         if (e instanceof NoSuchMethodException) {
4969           // if traversing up, and not Object, and not instance method
4970           // CH 070425 not sure why invokeOn needs to be null, removing
4971           // this
4972           if (callOnSupers /* && invokeOn == null */
4973               && !theClass.equals(Object.class)) {
4974             return callMethod(theClass.getSuperclass(), invokeOn,
4975                 methodName, paramTypesOrArrayOrList,
4976                 paramsOrListOrArray, callOnSupers, overrideSecurity);
4977           }
4978         }
4979         throw new RuntimeException("Problem calling method " + methodName
4980             + " on " + theClass.getName(), e);
4981       }
4982 
4983       return invokeMethod(method, invokeOn, paramsOrListOrArray);
4984     } catch (RuntimeException re) {
4985       String message = "Problem calling method " + methodName
4986             + " on " + (theClass == null ? null : theClass.getName());
4987       if (injectInException(re, message)) {
4988         throw re;
4989       }
4990       throw new RuntimeException(message, re);
4991     }
4992   }
4993 
4994   /** pass this in the invokeOn to signify no params */
4995   private static final Object NO_PARAMS = new Object();
4996 
4997   /**
4998    * Safely invoke a reflection method that takes no args
4999    *
5000    * @param method
5001    *            to invoke
5002    * @param invokeOn
5003    * if NO_PARAMS then will not pass in params.
5004    * @return the result
5005    */
5006   public static Object invokeMethod(Method method, Object invokeOn) {
5007     return invokeMethod(method, invokeOn, NO_PARAMS);
5008   }
5009 
5010   /**
5011    * Safely invoke a reflection method
5012    *
5013    * @param method
5014    *            to invoke
5015    * @param invokeOn
5016    * @param paramsOrListOrArray must be an arg.  If null, will pass null.
5017    * if NO_PARAMS then will not pass in params.
5018    * @return the result
5019    */
5020   public static Object invokeMethod(Method method, Object invokeOn,
5021       Object paramsOrListOrArray) {
5022 
5023     Object[] args = paramsOrListOrArray == NO_PARAMS ? null : (Object[]) toArray(paramsOrListOrArray);
5024 
5025     //we want to make sure things are accessible
5026     method.setAccessible(true);
5027 
5028     //only if the method exists, try to execute
5029     Object result = null;
5030     Exception e = null;
5031     try {
5032       result = method.invoke(invokeOn, args);
5033     } catch (IllegalAccessException iae) {
5034       e = iae;
5035     } catch (IllegalArgumentException iae) {
5036       e = iae;
5037     } catch (InvocationTargetException ite) {
5038       //this means the underlying call caused exception... its ok if runtime
5039       if (ite.getCause() instanceof RuntimeException) {
5040         throw (RuntimeException)ite.getCause();
5041       }
5042       //else throw as invocation target...
5043       e = ite;
5044     }
5045     if (e != null) {
5046       throw new RuntimeException("Cant execute reflection method: "
5047           + method.getName() + ", on: " + className(invokeOn)
5048           + ", with args: " + classNameCollection(args), e);
5049     }
5050     return result;
5051   }
5052 
5053   /**
5054    * Convert a list to an array with the type of the first element e.g. if it
5055    * is a list of Person objects, then the array is Person[]
5056    *
5057    * @param objectOrArrayOrCollection
5058    *            is a list
5059    * @return the array of objects with type of the first element in the list
5060    */
5061   public static Object toArray(Object objectOrArrayOrCollection) {
5062     // do this before length since if array with null in it, we want ti get
5063     // it back
5064     if (objectOrArrayOrCollection != null
5065         && objectOrArrayOrCollection.getClass().isArray()) {
5066       return objectOrArrayOrCollection;
5067     }
5068     int length = length(objectOrArrayOrCollection);
5069     if (length == 0) {
5070       return null;
5071     }
5072 
5073     if (objectOrArrayOrCollection instanceof Collection) {
5074       Collection<Object> collection = (Collection<Object>) objectOrArrayOrCollection;
5075       Object first = collection.iterator().next();
5076       Class<Object> theClass = first == null ? (Class)Object.class : (Class)first
5077           .getClass();
5078       return toArray(collection, theClass);
5079     }
5080     // make an array of the type of object passed in, size one
5081     Object array = Array.newInstance(objectOrArrayOrCollection.getClass(),
5082         1);
5083     Array.set(array, 0, objectOrArrayOrCollection);
5084     return array;
5085   }
5086 
5087   /**
5088    * convert a list into an array of type of theClass
5089    * @param <T> is the type of the array
5090    * @param collection list to convert
5091    * @param theClass type of array to return
5092    * @return array of type theClass[] filled with the objects from list
5093    */
5094   @SuppressWarnings("unchecked")
5095   public static <T> T[] toArray(Collection collection, Class<T> theClass) {
5096     if (collection == null || collection.size() == 0) {
5097       return null;
5098     }
5099 
5100     return (T[])collection.toArray((Object[]) Array.newInstance(theClass,
5101         collection.size()));
5102 
5103   }
5104 
5105   /**
5106    * helper method for calling a static method up the stack. method takes no
5107    * args (could be in superclass)
5108    *
5109    * @param theClass
5110    *            the class which has the method
5111    * @param methodName
5112    *            method name to call
5113    * @return the data
5114    */
5115   public static Object callMethod(Class theClass, String methodName) {
5116     return callMethod(theClass, null, methodName, null, null);
5117   }
5118 
5119   /**
5120    * helper method for calling a static method with no params
5121    *
5122    * @param theClass
5123    *            the class which has the method
5124    * @param methodName
5125    *            method name to call
5126    * @param callOnSupers
5127    *            if we should try the super classes if not exists in this class
5128    * @return the data
5129    */
5130   public static Object callMethod(Class theClass, String methodName,
5131       boolean callOnSupers) {
5132     return callMethod(theClass, null, methodName, null, null, callOnSupers);
5133   }
5134 
5135   /**
5136    * helper method for calling a static method up the stack
5137    *
5138    * @param theClass
5139    *            the class which has the method
5140    * @param methodName
5141    *            method name to call
5142    * @param paramTypesOrArrayOrList
5143    *            types of the params
5144    * @param paramsOrListOrArray
5145    *            data
5146    * @return the data
5147    */
5148   public static Object callMethod(Class theClass, String methodName,
5149       Object paramTypesOrArrayOrList, Object paramsOrListOrArray) {
5150     return callMethod(theClass, null, methodName, paramTypesOrArrayOrList,
5151         paramsOrListOrArray);
5152   }
5153 
5154   /**
5155    * helper method for calling a method with no params (could be in
5156    * superclass), will override security
5157    *
5158    * @param invokeOn
5159    *            instance to invoke on
5160    * @param methodName
5161    *            method name to call not exists in this class
5162    * @return the data
5163    */
5164   public static Object callMethod(Object invokeOn, String methodName) {
5165     if (invokeOn == null) {
5166       throw new NullPointerException("invokeOn is null: " + methodName);
5167     }
5168     return callMethod(invokeOn.getClass(), invokeOn, methodName, null,
5169         null, true, true);
5170   }
5171 
5172   /**
5173    * replace a string or strings from a string, and put the output in a string
5174    * buffer. This does not recurse
5175    *
5176    * @param text
5177    *            string to look in
5178    * @param searchFor
5179    *            string array to search for
5180    * @param replaceWith
5181    *            string array to replace with
5182    * @return the string
5183    */
5184   public static String replace(String text, Object searchFor,
5185       Object replaceWith) {
5186     return replace(null, null, text, searchFor, replaceWith, false, 0,
5187         false);
5188   }
5189 
5190   /**
5191    * replace a string or strings from a string, and put the output in a string
5192    * buffer
5193    *
5194    * @param text
5195    *            string to look in
5196    * @param searchFor
5197    *            string array to search for
5198    * @param replaceWith
5199    *            string array to replace with
5200    * @param recurse
5201    *            if true then do multiple replaces (on the replacements)
5202    * @return the string
5203    */
5204   public static String replace(String text, Object searchFor,
5205       Object replaceWith, boolean recurse) {
5206     return replace(null, null, text, searchFor, replaceWith, recurse,
5207         recurse ? length(searchFor) : 0, false);
5208   }
5209 
5210   /**
5211    * replace a string or strings from a string, and put the output in a string
5212    * buffer
5213    *
5214    * @param text
5215    *            string to look in
5216    * @param searchFor
5217    *            string array to search for
5218    * @param replaceWith
5219    *            string array to replace with
5220    * @param recurse
5221    *            if true then do multiple replaces (on the replacements)
5222    * @param removeIfFound
5223    *            true if removing from searchFor and replaceWith if found
5224    * @return the string
5225    */
5226   public static String replace(String text, Object searchFor,
5227       Object replaceWith, boolean recurse, boolean removeIfFound) {
5228     return replace(null, null, text, searchFor, replaceWith, recurse,
5229         recurse ? length(searchFor) : 0, removeIfFound);
5230   }
5231 
5232   /**
5233    * <p>
5234    * Replaces all occurrences of a String within another String.
5235    * </p>
5236    *
5237    * <p>
5238    * A <code>null</code> reference passed to this method is a no-op.
5239    * </p>
5240    *
5241    * <pre>
5242    * replace(null, *, *)        = null
5243    * replace(&quot;&quot;, *, *)          = &quot;&quot;
5244    * replace(&quot;any&quot;, null, *)    = &quot;any&quot;
5245    * replace(&quot;any&quot;, *, null)    = &quot;any&quot;
5246    * replace(&quot;any&quot;, &quot;&quot;, *)      = &quot;any&quot;
5247    * replace(&quot;aba&quot;, &quot;a&quot;, null)  = &quot;aba&quot;
5248    * replace(&quot;aba&quot;, &quot;a&quot;, &quot;&quot;)    = &quot;b&quot;
5249    * replace(&quot;aba&quot;, &quot;a&quot;, &quot;z&quot;)   = &quot;zbz&quot;
5250    * </pre>
5251    *
5252    * @see #replace(String text, String repl, String with, int max)
5253    * @param text
5254    *            text to search and replace in, may be null
5255    * @param repl
5256    *            the String to search for, may be null
5257    * @param with
5258    *            the String to replace with, may be null
5259    * @return the text with any replacements processed, <code>null</code> if
5260    *         null String input
5261    */
5262   public static String replace(String text, String repl, String with) {
5263     return replace(text, repl, with, -1);
5264   }
5265 
5266   /**
5267    * <p>
5268    * Replaces a String with another String inside a larger String, for the
5269    * first <code>max</code> values of the search String.
5270    * </p>
5271    *
5272    * <p>
5273    * A <code>null</code> reference passed to this method is a no-op.
5274    * </p>
5275    *
5276    * <pre>
5277    * replace(null, *, *, *)         = null
5278    * replace(&quot;&quot;, *, *, *)           = &quot;&quot;
5279    * replace(&quot;any&quot;, null, *, *)     = &quot;any&quot;
5280    * replace(&quot;any&quot;, *, null, *)     = &quot;any&quot;
5281    * replace(&quot;any&quot;, &quot;&quot;, *, *)       = &quot;any&quot;
5282    * replace(&quot;any&quot;, *, *, 0)        = &quot;any&quot;
5283    * replace(&quot;abaa&quot;, &quot;a&quot;, null, -1) = &quot;abaa&quot;
5284    * replace(&quot;abaa&quot;, &quot;a&quot;, &quot;&quot;, -1)   = &quot;b&quot;
5285    * replace(&quot;abaa&quot;, &quot;a&quot;, &quot;z&quot;, 0)   = &quot;abaa&quot;
5286    * replace(&quot;abaa&quot;, &quot;a&quot;, &quot;z&quot;, 1)   = &quot;zbaa&quot;
5287    * replace(&quot;abaa&quot;, &quot;a&quot;, &quot;z&quot;, 2)   = &quot;zbza&quot;
5288    * replace(&quot;abaa&quot;, &quot;a&quot;, &quot;z&quot;, -1)  = &quot;zbzz&quot;
5289    * </pre>
5290    *
5291    * @param text
5292    *            text to search and replace in, may be null
5293    * @param repl
5294    *            the String to search for, may be null
5295    * @param with
5296    *            the String to replace with, may be null
5297    * @param max
5298    *            maximum number of values to replace, or <code>-1</code> if
5299    *            no maximum
5300    * @return the text with any replacements processed, <code>null</code> if
5301    *         null String input
5302    */
5303   public static String replace(String text, String repl, String with, int max) {
5304     if (text == null || isEmpty(repl) || with == null || max == 0) {
5305       return text;
5306     }
5307 
5308     StringBuffer buf = new StringBuffer(text.length());
5309     int start = 0, end = 0;
5310     while ((end = text.indexOf(repl, start)) != -1) {
5311       buf.append(text.substring(start, end)).append(with);
5312       start = end + repl.length();
5313 
5314       if (--max == 0) {
5315         break;
5316       }
5317     }
5318     buf.append(text.substring(start));
5319     return buf.toString();
5320   }
5321 
5322   /**
5323    * <p>
5324    * Checks if a String is empty ("") or null.
5325    * </p>
5326    *
5327    * <pre>
5328    * isEmpty(null)      = true
5329    * isEmpty(&quot;&quot;)        = true
5330    * isEmpty(&quot; &quot;)       = false
5331    * isEmpty(&quot;bob&quot;)     = false
5332    * isEmpty(&quot;  bob  &quot;) = false
5333    * </pre>
5334    *
5335    * <p>
5336    * NOTE: This method changed in Lang version 2.0. It no longer trims the
5337    * String. That functionality is available in isBlank().
5338    * </p>
5339    *
5340    * @param str
5341    *            the String to check, may be null
5342    * @return <code>true</code> if the String is empty or null
5343    */
5344   public static boolean isEmpty(String str) {
5345     return str == null || str.length() == 0;
5346   }
5347 
5348   /**
5349    * replace a string or strings from a string, and put the output in a string
5350    * buffer. This does not recurse
5351    *
5352    * @param outBuffer
5353    *            stringbuffer to write to
5354    * @param text
5355    *            string to look in
5356    * @param searchFor
5357    *            string array to search for
5358    * @param replaceWith
5359    *            string array to replace with
5360    */
5361   public static void replace(StringBuffer outBuffer, String text,
5362       Object searchFor, Object replaceWith) {
5363     replace(outBuffer, null, text, searchFor, replaceWith, false, 0, false);
5364   }
5365 
5366   /**
5367    * replace a string or strings from a string, and put the output in a string
5368    * buffer
5369    *
5370    * @param outBuffer
5371    *            stringbuffer to write to
5372    * @param text
5373    *            string to look in
5374    * @param searchFor
5375    *            string array to search for
5376    * @param replaceWith
5377    *            string array to replace with
5378    * @param recurse
5379    *            if true then do multiple replaces (on the replacements)
5380    */
5381   public static void replace(StringBuffer outBuffer, String text,
5382       Object searchFor, Object replaceWith, boolean recurse) {
5383     replace(outBuffer, null, text, searchFor, replaceWith, recurse,
5384         recurse ? length(searchFor) : 0, false);
5385   }
5386 
5387   /**
5388    * replace a string with other strings, and either write to outWriter, or
5389    * StringBuffer, and if StringBuffer potentially return a string. If
5390    * outBuffer and outWriter are null, then return the String
5391    *
5392    * @param outBuffer
5393    *            stringbuffer to write to, or null to not
5394    * @param outWriter
5395    *            Writer to write to, or null to not.
5396    * @param text
5397    *            string to look in
5398    * @param searchFor
5399    *            string array to search for, or string, or list
5400    * @param replaceWith
5401    *            string array to replace with, or string, or list
5402    * @param recurse
5403    *            if true then do multiple replaces (on the replacements)
5404    * @param timeToLive
5405    *            if recursing, prevent endless loops
5406    * @param removeIfFound
5407    *            true if removing from searchFor and replaceWith if found
5408    * @return the String if outBuffer and outWriter are null
5409    * @throws IndexOutOfBoundsException
5410    *             if the lengths of the arrays are not the same (null is ok,
5411    *             and/or size 0)
5412    * @throws IllegalArgumentException
5413    *             if the search is recursive and there is an endless loop due
5414    *             to outputs of one being inputs to another
5415    */
5416   private static String replace(StringBuffer outBuffer, Writer outWriter,
5417       String text, Object searchFor, Object replaceWith, boolean recurse,
5418       int timeToLive, boolean removeIfFound) {
5419 
5420     // if recursing, we need to get the string, then print to buffer (since
5421     // we need multiple passes)
5422     if (!recurse) {
5423       return replaceHelper(outBuffer, outWriter, text, searchFor,
5424           replaceWith, recurse, timeToLive, removeIfFound);
5425     }
5426     // get the string
5427     String result = replaceHelper(null, null, text, searchFor, replaceWith,
5428         recurse, timeToLive, removeIfFound);
5429     if (outBuffer != null) {
5430       outBuffer.append(result);
5431       return null;
5432     }
5433 
5434     if (outWriter != null) {
5435       try {
5436         outWriter.write(result);
5437       } catch (IOException ioe) {
5438         throw new RuntimeException(ioe);
5439       }
5440       return null;
5441     }
5442 
5443     return result;
5444 
5445   }
5446 
5447   /**
5448    * replace a string or strings from a string, and put the output in a string
5449    * buffer. This does not recurse
5450    *
5451    * @param outWriter
5452    *            writer to write to
5453    * @param text
5454    *            string to look in
5455    * @param searchFor
5456    *            string array to search for
5457    * @param replaceWith
5458    *            string array to replace with
5459    */
5460   public static void replace(Writer outWriter, String text, Object searchFor,
5461       Object replaceWith) {
5462     replace(null, outWriter, text, searchFor, replaceWith, false, 0, false);
5463   }
5464 
5465   /**
5466    * replace a string or strings from a string, and put the output in a string
5467    * buffer
5468    *
5469    * @param outWriter
5470    *            writer to write to
5471    * @param text
5472    *            string to look in
5473    * @param searchFor
5474    *            string array to search for
5475    * @param replaceWith
5476    *            string array to replace with
5477    * @param recurse
5478    *            if true then do multiple replaces (on the replacements)
5479    */
5480   public static void replace(Writer outWriter, String text, Object searchFor,
5481       Object replaceWith, boolean recurse) {
5482     replace(null, outWriter, text, searchFor, replaceWith, recurse,
5483         recurse ? length(searchFor) : 0, false);
5484   }
5485 
5486   /**
5487    * replace a string with other strings, and either write to outWriter, or
5488    * StringBuffer, and if StringBuffer potentially return a string. If
5489    * outBuffer and outWriter are null, then return the String
5490    *
5491    * @param outBuffer
5492    *            stringbuffer to write to, or null to not
5493    * @param outWriter
5494    *            Writer to write to, or null to not.
5495    * @param text
5496    *            string to look in
5497    * @param searchFor
5498    *            string array to search for, or string, or list
5499    * @param replaceWith
5500    *            string array to replace with, or string, or list
5501    * @param recurse
5502    *            if true then do multiple replaces (on the replacements)
5503    * @param timeToLive
5504    *            if recursing, prevent endless loops
5505    * @param removeIfFound
5506    *            true if removing from searchFor and replaceWith if found
5507    * @return the String if outBuffer and outWriter are null
5508    * @throws IllegalArgumentException
5509    *             if the search is recursive and there is an endless loop due
5510    *             to outputs of one being inputs to another
5511    * @throws IndexOutOfBoundsException
5512    *             if the lengths of the arrays are not the same (null is ok,
5513    *             and/or size 0)
5514    */
5515   private static String replaceHelper(StringBuffer outBuffer,
5516       Writer outWriter, String text, Object searchFor,
5517       Object replaceWith, boolean recurse, int timeToLive,
5518       boolean removeIfFound) {
5519 
5520     try {
5521       // if recursing, this shouldnt be less than 0
5522       if (timeToLive < 0) {
5523         throw new IllegalArgumentException("TimeToLive under 0: "
5524             + timeToLive + ", " + text);
5525       }
5526 
5527       int searchForLength = length(searchFor);
5528       boolean done = false;
5529       // no need to do anything
5530       if (isEmpty(text)) {
5531         return text;
5532       }
5533       // need to write the input to output, later
5534       if (searchForLength == 0) {
5535         done = true;
5536       }
5537 
5538       boolean[] noMoreMatchesForReplIndex = null;
5539       int inputIndex = -1;
5540       int replaceIndex = -1;
5541       long resultPacked = -1;
5542 
5543       if (!done) {
5544         // make sure lengths are ok, these need to be equal
5545         if (searchForLength != length(replaceWith)) {
5546           throw new IndexOutOfBoundsException("Lengths dont match: "
5547               + searchForLength + ", " + length(replaceWith));
5548         }
5549 
5550         // keep track of which still have matches
5551         noMoreMatchesForReplIndex = new boolean[searchForLength];
5552 
5553         // index of replace array that will replace the search string
5554         // found
5555 
5556 
5557         resultPacked = findNextIndexHelper(searchForLength, searchFor,
5558             replaceWith,
5559             noMoreMatchesForReplIndex, text, 0);
5560 
5561         inputIndex = unpackInt(resultPacked, true);
5562         replaceIndex = unpackInt(resultPacked, false);
5563       }
5564 
5565       // get a good guess on the size of the result buffer so it doesnt
5566       // have to double if it
5567       // goes over a bit
5568       boolean writeToWriter = outWriter != null;
5569 
5570       // no search strings found, we are done
5571       if (done || inputIndex == -1) {
5572         if (writeToWriter) {
5573           outWriter.write(text, 0, text.length());
5574           return null;
5575         }
5576         if (outBuffer != null) {
5577           appendSubstring(outBuffer, text, 0, text.length());
5578           return null;
5579         }
5580         return text;
5581       }
5582 
5583       // no buffer if writing to writer
5584       StringBuffer bufferToWriteTo = outBuffer != null ? outBuffer
5585           : (writeToWriter ? null : new StringBuffer(text.length()
5586               + replaceStringsBufferIncrease(text, searchFor,
5587                   replaceWith)));
5588 
5589       String searchString = null;
5590       String replaceString = null;
5591 
5592       int start = 0;
5593 
5594       while (inputIndex != -1) {
5595 
5596         searchString = (String) get(searchFor, replaceIndex);
5597         replaceString = (String) get(replaceWith, replaceIndex);
5598         if (writeToWriter) {
5599           outWriter.write(text, start, inputIndex - start);
5600           outWriter.write(replaceString);
5601         } else {
5602           appendSubstring(bufferToWriteTo, text, start, inputIndex)
5603               .append(replaceString);
5604         }
5605 
5606         if (removeIfFound) {
5607           // better be an iterator based find replace
5608           searchFor = remove(searchFor, replaceIndex);
5609           replaceWith = remove(replaceWith, replaceIndex);
5610           noMoreMatchesForReplIndex = (boolean[])remove(noMoreMatchesForReplIndex, replaceIndex);
5611           // we now have a lesser size if we removed one
5612           searchForLength--;
5613         }
5614 
5615         start = inputIndex + searchString.length();
5616 
5617         resultPacked = findNextIndexHelper(searchForLength, searchFor,
5618             replaceWith,
5619             noMoreMatchesForReplIndex, text, start);
5620         inputIndex = unpackInt(resultPacked, true);
5621         replaceIndex = unpackInt(resultPacked, false);
5622       }
5623       if (writeToWriter) {
5624         outWriter.write(text, start, text.length() - start);
5625 
5626       } else {
5627         appendSubstring(bufferToWriteTo, text, start, text.length());
5628       }
5629 
5630       // no need to convert to string if incoming buffer or writer
5631       if (writeToWriter || outBuffer != null) {
5632         if (recurse) {
5633           throw new IllegalArgumentException(
5634               "Cannot recurse and write to existing buffer or writer!");
5635         }
5636         return null;
5637       }
5638       String resultString = bufferToWriteTo.toString();
5639 
5640       if (recurse) {
5641         return replaceHelper(outBuffer, outWriter, resultString,
5642             searchFor, replaceWith, recurse, timeToLive - 1, false);
5643       }
5644       // this might be null for writer
5645       return resultString;
5646     } catch (IOException ioe) {
5647       throw new RuntimeException(ioe);
5648     }
5649   }
5650 
5651   /**
5652    * give a best guess on buffer increase for String[] replace get a good
5653    * guess on the size of the result buffer so it doesnt have to double if it
5654    * goes over a bit
5655    *
5656    * @param text
5657    * @param repl
5658    * @param with
5659    * @return the increase, with 20% cap
5660    */
5661   static int replaceStringsBufferIncrease(String text, Object repl,
5662       Object with) {
5663     // count the greaters
5664     int increase = 0;
5665     Iterator iteratorReplace = iterator(repl);
5666     Iterator iteratorWith = iterator(with);
5667     int replLength = length(repl);
5668     String currentRepl = null;
5669     String currentWith = null;
5670     for (int i = 0; i < replLength; i++) {
5671       currentRepl = (String) next(repl, iteratorReplace, i);
5672       currentWith = (String) next(with, iteratorWith, i);
5673       if (currentRepl == null || currentWith == null) {
5674         throw new NullPointerException("Replace string is null: "
5675             + text + ", " + currentRepl + ", " + currentWith);
5676       }
5677       int greater = currentWith.length() - currentRepl.length();
5678       increase += greater > 0 ? 3 * greater : 0; // assume 3 matches
5679     }
5680     // have upper-bound at 20% increase, then let Java take over
5681     increase = Math.min(increase, text.length() / 5);
5682     return increase;
5683   }
5684 
5685   /**
5686    * Helper method to find the next match in an array of strings replace
5687    *
5688    * @param searchForLength
5689    * @param searchFor
5690    * @param replaceWith
5691    * @param noMoreMatchesForReplIndex
5692    * @param input
5693    * @param start
5694    *            is where to start looking
5695    * @return result packed into a long, inputIndex first, then replaceIndex
5696    */
5697   private static long findNextIndexHelper(int searchForLength,
5698       Object searchFor, Object replaceWith, boolean[] noMoreMatchesForReplIndex,
5699       String input, int start) {
5700 
5701     int inputIndex = -1;
5702     int replaceIndex = -1;
5703 
5704     Iterator iteratorSearchFor = iterator(searchFor);
5705     Iterator iteratorReplaceWith = iterator(replaceWith);
5706 
5707     String currentSearchFor = null;
5708     String currentReplaceWith = null;
5709     int tempIndex = -1;
5710     for (int i = 0; i < searchForLength; i++) {
5711       currentSearchFor = (String) next(searchFor, iteratorSearchFor, i);
5712       currentReplaceWith = (String) next(replaceWith,
5713           iteratorReplaceWith, i);
5714       if (noMoreMatchesForReplIndex[i] || isEmpty(currentSearchFor)
5715           || currentReplaceWith == null) {
5716         continue;
5717       }
5718       tempIndex = input.indexOf(currentSearchFor, start);
5719 
5720       // see if we need to keep searching for this
5721       noMoreMatchesForReplIndex[i] = tempIndex == -1;
5722 
5723       if (tempIndex != -1 && (inputIndex == -1 || tempIndex < inputIndex)) {
5724         inputIndex = tempIndex;
5725         replaceIndex = i;
5726       }
5727 
5728     }
5729     // dont create an array, no more objects
5730     long resultPacked = packInts(inputIndex, replaceIndex);
5731     return resultPacked;
5732   }
5733 
5734   /**
5735    * pack two ints into a long. Note: the first is held in the left bits, the
5736    * second is held in the right bits
5737    *
5738    * @param first
5739    *            is first int
5740    * @param second
5741    *            is second int
5742    * @return the long which has two ints in there
5743    */
5744   public static long packInts(int first, int second) {
5745     long result = first;
5746     result <<= 32;
5747     result |= second;
5748     return result;
5749   }
5750 
5751   /**
5752    * take a long
5753    *
5754    * @param theLong
5755    *            to unpack
5756    * @param isFirst
5757    *            true for first, false for second
5758    * @return one of the packed ints, first or second
5759    */
5760   public static int unpackInt(long theLong, boolean isFirst) {
5761 
5762     int result = 0;
5763     // put this in the position of the second one
5764     if (isFirst) {
5765       theLong >>= 32;
5766     }
5767     // only look at right part
5768     result = (int) (theLong & 0xffffffff);
5769     return result;
5770   }
5771 
5772   /**
5773    * append a substring to a stringbuffer. removes dependency on substring
5774    * which creates objects
5775    *
5776    * @param buf
5777    *            stringbuffer
5778    * @param string
5779    *            source string
5780    * @param start
5781    *            start index of source string
5782    * @param end
5783    *            end index of source string
5784    * @return the string buffer for chaining
5785    */
5786   private static StringBuffer appendSubstring(StringBuffer buf,
5787       String string, int start, int end) {
5788     for (int i = start; i < end; i++) {
5789       buf.append(string.charAt(i));
5790     }
5791     return buf;
5792   }
5793 
5794   /**
5795    * Get a specific index of an array or collection (note for collections and
5796    * iterating, it is more efficient to get an iterator and iterate
5797    *
5798    * @param arrayOrCollection
5799    * @param index
5800    * @return the object at that index
5801    */
5802   public static Object get(Object arrayOrCollection, int index) {
5803 
5804     if (arrayOrCollection == null) {
5805       if (index == 0) {
5806         return null;
5807       }
5808       throw new RuntimeException("Trying to access index " + index
5809           + " of null");
5810     }
5811 
5812     // no need to iterator on list (e.g. FastProxyList has no iterator
5813     if (arrayOrCollection instanceof List) {
5814       return ((List) arrayOrCollection).get(index);
5815     }
5816     if (arrayOrCollection instanceof Collection) {
5817       Iterator iterator = iterator(arrayOrCollection);
5818       for (int i = 0; i < index; i++) {
5819         next(arrayOrCollection, iterator, i);
5820       }
5821       return next(arrayOrCollection, iterator, index);
5822     }
5823 
5824     if (arrayOrCollection.getClass().isArray()) {
5825       return Array.get(arrayOrCollection, index);
5826     }
5827 
5828     if (index == 0) {
5829       return arrayOrCollection;
5830     }
5831 
5832     throw new RuntimeException("Trying to access index " + index
5833         + " of and object: " + arrayOrCollection);
5834   }
5835 
5836   /**
5837    * convert a collection of sources to a string
5838    * @param sources
5839    * @return the string
5840    */
5841   public static String toString(Collection<Source> sources) {
5842     if (length(sources) == 0) {
5843       return null;
5844     }
5845     StringBuilder result = new StringBuilder();
5846     for (Source source : sources) {
5847       result.append(source.getId()).append(", ");
5848     }
5849     result.delete(result.length()-2, result.length());
5850     return result.toString();
5851   }
5852 
5853   /**
5854    * fail safe toString for Exception blocks, and include the stack
5855    * if there is a problem with toString()
5856    * @param object
5857    * @return the toStringSafe string
5858    */
5859   public static String toStringSafe(Object object) {
5860     if (object == null) {
5861       return null;
5862     }
5863 
5864     try {
5865       //give size and type if collection
5866       if (object instanceof Collection) {
5867         Collection<Object> collection = (Collection<Object>) object;
5868         int collectionSize = collection.size();
5869         if (collectionSize == 0) {
5870           return "Empty " + object.getClass().getSimpleName();
5871         }
5872         Object first = collection.iterator().next();
5873         return object.getClass().getSimpleName() + " of size "
5874           + collectionSize + " with first type: " +
5875           (first == null ? null : first.getClass());
5876       }
5877 
5878       return object.toString();
5879     } catch (Exception e) {
5880       return "<<exception>> " + object.getClass() + ":\n" + getFullStackTrace(e) + "\n";
5881     }
5882   }
5883 
5884   /**
5885    * get the boolean value for an object, cant be null or blank
5886    *
5887    * @param object
5888    * @return the boolean
5889    */
5890   public static boolean booleanValue(Object object) {
5891     // first handle blanks
5892     if (nullOrBlank(object)) {
5893       throw new RuntimeException(
5894           "Expecting something which can be converted to boolean, but is null or blank: '"
5895               + object + "'");
5896     }
5897     // its not blank, just convert
5898     if (object instanceof Boolean) {
5899       return (Boolean) object;
5900     }
5901     if (object instanceof String) {
5902       String string = (String) object;
5903       if (equalsIgnoreCase(string, "true")
5904           || equalsIgnoreCase(string, "t")
5905           || equalsIgnoreCase(string, "yes")
5906           || equalsIgnoreCase(string, "on")
5907           || equalsIgnoreCase(string, "y")) {
5908         return true;
5909       }
5910       if (equalsIgnoreCase(string, "false")
5911           || equalsIgnoreCase(string, "f")
5912           || equalsIgnoreCase(string, "no")
5913           || equalsIgnoreCase(string, "off")
5914           || equalsIgnoreCase(string, "n")) {
5915         return false;
5916       }
5917       throw new RuntimeException(
5918           "Invalid string to boolean conversion: '" + string
5919               + "' expecting true|false or t|f or yes|no or y|n case insensitive");
5920 
5921     }
5922     throw new RuntimeException("Cant convert object to boolean: "
5923         + object.getClass());
5924 
5925   }
5926 
5927   /**
5928    * get the boolean value for an object
5929    *
5930    * @param object
5931    * @param defaultBoolean
5932    *            if object is null or empty
5933    * @return the boolean
5934    */
5935   public static boolean booleanValue(Object object, boolean defaultBoolean) {
5936     if (nullOrBlank(object)) {
5937       return defaultBoolean;
5938     }
5939     return booleanValue(object);
5940   }
5941 
5942   /**
5943    * get the Boolean value for an object
5944    *
5945    * @param object
5946    * @return the Boolean or null if null or empty
5947    */
5948   public static Boolean booleanObjectValue(Object object) {
5949     if (nullOrBlank(object)) {
5950       return null;
5951     }
5952     return booleanValue(object);
5953   }
5954 
5955   /**
5956    * is an object null or blank
5957    *
5958    * @param object
5959    * @return true if null or blank
5960    */
5961   public static boolean nullOrBlank(Object object) {
5962     // first handle blanks and nulls
5963     if (object == null) {
5964       return true;
5965     }
5966     if (object instanceof String && isBlank(((String) object))) {
5967       return true;
5968     }
5969     return false;
5970 
5971   }
5972 
5973   /**
5974    * get a getter method object for a class, potentially in superclasses
5975    * @param theClass
5976    * @param fieldName
5977    * @param callOnSupers true if superclasses should be looked in for the getter
5978    * @param throwExceptionIfNotFound will throw runtime exception if not found
5979    * @return the getter object or null if not found (or exception if param is set)
5980    */
5981   public static Method getter(Class theClass, String fieldName, boolean callOnSupers,
5982       boolean throwExceptionIfNotFound) {
5983     String getterName = getterNameFromPropertyName(theClass, fieldName);
5984     return getterHelper(theClass, fieldName, getterName, callOnSupers, throwExceptionIfNotFound);
5985   }
5986 
5987   /**
5988    * get a setter method object for a class, potentially in superclasses
5989    * @param theClass
5990    * @param fieldName
5991    * @param getterName name of setter
5992    * @param callOnSupers true if superclasses should be looked in for the setter
5993    * @param throwExceptionIfNotFound will throw runtime exception if not found
5994    * @return the setter object or null if not found (or exception if param is set)
5995    */
5996   public static Method getterHelper(Class theClass, String fieldName, String getterName,
5997       boolean callOnSupers, boolean throwExceptionIfNotFound) {
5998     Method[] methods = retrieveDeclaredMethods(theClass);
5999     if (methods != null) {
6000       for (Method method : methods) {
6001         if (equals(getterName, method.getName()) && isGetter(method)) {
6002           return method;
6003         }
6004       }
6005     }
6006     //if method not found
6007     //if traversing up, and not Object, and not instance method
6008     if (callOnSupers && !theClass.equals(Object.class)) {
6009       return getterHelper(theClass.getSuperclass(), fieldName, getterName,
6010           callOnSupers, throwExceptionIfNotFound);
6011     }
6012     //maybe throw an exception
6013     if (throwExceptionIfNotFound) {
6014       throw new PropertyDoesNotExistUnchecked("Cant find getter: "
6015           + getterName + ", in: " + theClass
6016           + ", callOnSupers: " + callOnSupers);
6017     }
6018     return null;
6019   }
6020 
6021   /**
6022    * generate getBb from bb
6023    * @param propertyName
6024    * @return the getter
6025    */
6026   public static String getterNameFromPropertyName(Class theClass, String propertyName) {
6027     
6028     Set<Field> fields = fields(theClass, null, null, false);
6029     for (Field field : nonNull(fields)) {
6030       if (propertyName.equals(field.getName())) {
6031         if (boolean.class.equals(field.getType())) {
6032           return "is" + capitalize(propertyName);
6033         }
6034         break;
6035       }
6036     }
6037     String getterName = "get" + capitalize(propertyName);
6038     String iserName = "is" + capitalize(propertyName);
6039     Set<String> methodNames = methodNames(theClass, null, false, false);
6040     if (methodNames.contains(iserName)) {
6041       return iserName;
6042     }
6043     return getterName;
6044   }
6045 
6046   /**
6047    * get all getters from a class, including superclasses (if specified) (up to and including the specified superclass).
6048    * ignore a certain marker annotation, or only include it.
6049    * Dont get static or final getters, and get getters of all types
6050    * @param theClass to look for fields in
6051    * @param superclassToStopAt to go up to or null to go up to Object
6052    * @param markerAnnotation if this is not null, then if the field has this annotation, then do not
6053    * include in list (if includeAnnotation is false)
6054    * @param includeAnnotation true if the attribute should be included if annotation is present, false if exclude
6055    * @return the set of field names or empty set if none
6056    */
6057   @SuppressWarnings("unchecked")
6058   public static Set<Method> getters(Class theClass, Class superclassToStopAt,
6059       Class<? extends Annotation> markerAnnotation, Boolean includeAnnotation) {
6060     return gettersHelper(theClass, superclassToStopAt, null, true,
6061         markerAnnotation, includeAnnotation);
6062   }
6063 
6064   /**
6065    * get all getters from a class, including superclasses (if specified)
6066    * @param theClass to look for fields in
6067    * @param superclassToStopAt to go up to or null to go up to Object
6068    * @param fieldType is the type of the field to get
6069    * @param includeSuperclassToStopAt if we should include the superclass
6070    * @param markerAnnotation if this is not null, then if the field has this annotation, then do not
6071    * include in list (if includeAnnotation is false)
6072    * @param includeAnnotation true if the attribute should be included if annotation is present, false if exclude
6073    * @return the set of fields (wont return null)
6074    */
6075   @SuppressWarnings("unchecked")
6076   static Set<Method> gettersHelper(Class theClass, Class superclassToStopAt, Class<?> fieldType,
6077       boolean includeSuperclassToStopAt,
6078       Class<? extends Annotation> markerAnnotation, Boolean includeAnnotation) {
6079     //MAKE SURE IF ANY MORE PARAMS ARE ADDED, THE CACHE KEY IS CHANGED!
6080 
6081     Set<Method> getterSet = null;
6082     String cacheKey = theClass + CACHE_SEPARATOR + superclassToStopAt + CACHE_SEPARATOR + fieldType + CACHE_SEPARATOR
6083       + includeSuperclassToStopAt + CACHE_SEPARATOR + markerAnnotation + CACHE_SEPARATOR + includeAnnotation;
6084     getterSet = getterSetCache().get(cacheKey);
6085     if (getterSet != null) {
6086       return getterSet;
6087     }
6088 
6089     getterSet = new LinkedHashSet<Method>();
6090     gettersHelper(theClass, superclassToStopAt, fieldType, includeSuperclassToStopAt,
6091         markerAnnotation, getterSet, includeAnnotation);
6092 
6093     //add to cache
6094     getterSetCache().put(cacheKey, getterSet);
6095 
6096     return getterSet;
6097 
6098   }
6099 
6100   /**
6101    * get all getters from a class, including superclasses (if specified)
6102    * @param theClass to look for fields in
6103    * @param superclassToStopAt to go up to or null to go up to Object
6104    * @param propertyType is the type of the field to get
6105    * @param includeSuperclassToStopAt if we should include the superclass
6106    * @param markerAnnotation if this is not null, then if the field has this annotation, then do not
6107    * include in list
6108    * @param getterSet set to add fields to
6109    * @param includeAnnotation if include or exclude
6110    */
6111   @SuppressWarnings("unchecked")
6112   private static void gettersHelper(Class theClass, Class superclassToStopAt, Class<?> propertyType,
6113       boolean includeSuperclassToStopAt,
6114       Class<? extends Annotation> markerAnnotation, Set<Method> getterSet, Boolean includeAnnotation) {
6115     theClass = unenhanceClass(theClass);
6116     Method[] methods = retrieveDeclaredMethods(theClass);
6117     if (length(methods) != 0) {
6118       
6119       //sort to put in right order... java7 doesnt do this
6120       List<Method> methodsList = new ArrayList<Method>();
6121       
6122       for (Method method: methods) {
6123         methodsList.add(method);
6124       }
6125       
6126       Collections.sort(methodsList, new Comparator<Method>() {
6127 
6128         @Override
6129         public int compare(Method o1, Method o2) {
6130           return o1.getName().compareTo(o2.getName());
6131         }
6132       });
6133       
6134       for (Method method: methodsList) {
6135         //must be a getter
6136         if (!isGetter(method)) {
6137           continue;
6138         }
6139         //if checking for annotation
6140         if (markerAnnotation != null
6141             && (includeAnnotation != method.isAnnotationPresent(markerAnnotation))) {
6142           continue;
6143         }
6144         //if checking for type, and not right type, continue
6145         if (propertyType != null && !propertyType.isAssignableFrom(method.getReturnType())) {
6146           continue;
6147         }
6148 
6149         //go for it
6150         getterSet.add(method);
6151       }
6152     }
6153     //see if done recursing (if superclassToStopAt is null, then stop at Object
6154     if (theClass.equals(superclassToStopAt) || theClass.equals(Object.class)) {
6155       return;
6156     }
6157     Class superclass = theClass.getSuperclass();
6158     if (!includeSuperclassToStopAt && superclass.equals(superclassToStopAt)) {
6159       return;
6160     }
6161     //recurse
6162     gettersHelper(superclass, superclassToStopAt, propertyType,
6163         includeSuperclassToStopAt, markerAnnotation, getterSet,
6164         includeAnnotation);
6165   }
6166 
6167   /**
6168    * if the method name starts with get, and takes no args, and returns something,
6169    * then getter
6170    * @param method
6171    * @return true if getter
6172    */
6173   public static boolean isGetter(Method method) {
6174 
6175     //must start with get
6176     String methodName = method.getName();
6177     if (!methodName.startsWith("get") && !methodName.startsWith("is")) {
6178       return false;
6179     }
6180 
6181     //must not be void
6182     if (method.getReturnType() == Void.TYPE) {
6183       return false;
6184     }
6185 
6186     //must not take args
6187     if (length(method.getParameterTypes()) != 0) {
6188       return false;
6189     }
6190 
6191     //must not be static
6192     if (Modifier.isStatic(method.getModifiers())) {
6193       return false;
6194     }
6195 
6196     return true;
6197   }
6198 
6199   /**
6200    * assign data to a setter.  Will find the field in superclasses, will typecast,
6201    * and will override security (private, protected, etc)
6202    * @param invokeOn to call on or null for static
6203    * @param fieldName method name to call
6204    * @param dataToAssign data
6205    * @param typeCast will typecast if true
6206    * @throws PropertyDoesNotExistUnchecked if not there
6207    */
6208   public static void assignSetter(Object invokeOn,
6209       String fieldName, Object dataToAssign, boolean typeCast) {
6210     Class invokeOnClass = invokeOn.getClass();
6211     try {
6212       Method setter = setter(invokeOnClass, fieldName, true, true);
6213       setter.setAccessible(true);
6214       if (typeCast) {
6215         dataToAssign = typeCast(dataToAssign, setter.getParameterTypes()[0]);
6216       }
6217       setter.invoke(invokeOn, new Object[]{dataToAssign});
6218     } catch (Exception e) {
6219       throw new RuntimeException("Problem assigning setter: " + fieldName
6220           + " on class: " + invokeOnClass + ", type of data is: " + className(dataToAssign), e);
6221     }
6222   }
6223 
6224   /**
6225    * if the method name starts with get, and takes no args, and returns something,
6226    * then getter
6227    * @param method
6228    * @return true if getter
6229    */
6230   public static boolean isSetter(Method method) {
6231 
6232     //must start with get
6233     if (!method.getName().startsWith("set")) {
6234       return false;
6235     }
6236 
6237     //must be void
6238     if (method.getReturnType() != Void.TYPE) {
6239       return false;
6240     }
6241 
6242     //must take one arg
6243     if (length(method.getParameterTypes()) != 1) {
6244       return false;
6245     }
6246 
6247     //must not be static
6248     if (Modifier.isStatic(method.getModifiers())) {
6249       return false;
6250     }
6251 
6252     return true;
6253   }
6254 
6255   /**
6256    * get a setter method object for a class, potentially in superclasses
6257    * @param theClass
6258    * @param fieldName
6259    * @param callOnSupers true if superclasses should be looked in for the setter
6260    * @param throwExceptionIfNotFound will throw runtime exception if not found
6261    * @return the setter object or null if not found (or exception if param is set)
6262    */
6263   public static Method setter(Class theClass, String fieldName, boolean callOnSupers,
6264       boolean throwExceptionIfNotFound) {
6265     String setterName = setterNameFromPropertyName(fieldName);
6266     return setterHelper(theClass, fieldName, setterName, callOnSupers, throwExceptionIfNotFound);
6267   }
6268 
6269   /**
6270    * get a setter method object for a class, potentially in superclasses
6271    * @param theClass
6272    * @param fieldName
6273    * @param setterName name of setter
6274    * @param callOnSupers true if superclasses should be looked in for the setter
6275    * @param throwExceptionIfNotFound will throw runtime exception if not found
6276    * @return the setter object or null if not found (or exception if param is set)
6277    */
6278   public static Method setterHelper(Class theClass, String fieldName, String setterName,
6279       boolean callOnSupers, boolean throwExceptionIfNotFound) {
6280     Method[] methods = retrieveDeclaredMethods(theClass);
6281     if (methods != null) {
6282       for (Method method : methods) {
6283         if (equals(setterName, method.getName()) && isSetter(method)) {
6284           return method;
6285         }
6286       }
6287     }
6288     //if method not found
6289     //if traversing up, and not Object, and not instance method
6290     if (callOnSupers && !theClass.equals(Object.class)) {
6291       return setterHelper(theClass.getSuperclass(), fieldName, setterName,
6292           callOnSupers, throwExceptionIfNotFound);
6293     }
6294     //maybe throw an exception
6295     if (throwExceptionIfNotFound) {
6296       throw new PropertyDoesNotExistUnchecked("Cant find setter: "
6297           + setterName + ", in: " + theClass
6298           + ", callOnSupers: " + callOnSupers);
6299     }
6300     return null;
6301   }
6302 
6303   /**
6304    * generate setBb from bb
6305    * @param propertyName
6306    * @return the setter
6307    */
6308   public static String setterNameFromPropertyName(String propertyName) {
6309     return "set" + capitalize(propertyName);
6310   }
6311 
6312   /**
6313    * get all setters from a class, including superclasses (if specified)
6314    * @param theClass to look for fields in
6315    * @param superclassToStopAt to go up to or null to go up to Object
6316    * @param fieldType is the type of the field to get
6317    * @param includeSuperclassToStopAt if we should include the superclass
6318    * @param markerAnnotation if this is not null, then if the field has this annotation, then do not
6319    * include in list (if includeAnnotation is false)
6320    * @param includeAnnotation true if the attribute should be included if annotation is present, false if exclude
6321    * @return the set of fields (wont return null)
6322    */
6323   @SuppressWarnings("unchecked")
6324   public static Set<Method> setters(Class theClass, Class superclassToStopAt, Class<?> fieldType,
6325       boolean includeSuperclassToStopAt,
6326       Class<? extends Annotation> markerAnnotation, boolean includeAnnotation) {
6327     return settersHelper(theClass, superclassToStopAt, fieldType,
6328         includeSuperclassToStopAt, markerAnnotation, includeAnnotation);
6329   }
6330 
6331   /**
6332    * get all setters from a class, including superclasses (if specified)
6333    * @param theClass to look for fields in
6334    * @param superclassToStopAt to go up to or null to go up to Object
6335    * @param fieldType is the type of the field to get
6336    * @param includeSuperclassToStopAt if we should include the superclass
6337    * @param markerAnnotation if this is not null, then if the field has this annotation, then do not
6338    * include in list (if includeAnnotation is false)
6339    * @param includeAnnotation true if the attribute should be included if annotation is present, false if exclude
6340    * @return the set of fields (wont return null)
6341    */
6342   @SuppressWarnings("unchecked")
6343   static Set<Method> settersHelper(Class theClass, Class superclassToStopAt, Class<?> fieldType,
6344       boolean includeSuperclassToStopAt,
6345       Class<? extends Annotation> markerAnnotation, boolean includeAnnotation) {
6346     //MAKE SURE IF ANY MORE PARAMS ARE ADDED, THE CACHE KEY IS CHANGED!
6347 
6348     Set<Method> setterSet = null;
6349     String cacheKey = theClass + CACHE_SEPARATOR + superclassToStopAt + CACHE_SEPARATOR + fieldType + CACHE_SEPARATOR
6350       + includeSuperclassToStopAt + CACHE_SEPARATOR + markerAnnotation + CACHE_SEPARATOR + includeAnnotation;
6351     setterSet = setterSetCache().get(cacheKey);
6352     if (setterSet != null) {
6353       return setterSet;
6354     }
6355 
6356     setterSet = new LinkedHashSet<Method>();
6357     settersHelper(theClass, superclassToStopAt, fieldType, includeSuperclassToStopAt,
6358         markerAnnotation, setterSet, includeAnnotation);
6359 
6360     //add to cache
6361     setterSetCache().put(cacheKey, setterSet);
6362 
6363     return setterSet;
6364 
6365   }
6366 
6367   /**
6368    * get all setters from a class, including superclasses (if specified)
6369    * @param theClass to look for fields in
6370    * @param superclassToStopAt to go up to or null to go up to Object
6371    * @param propertyType is the type of the field to get
6372    * @param includeSuperclassToStopAt if we should include the superclass
6373    * @param markerAnnotation if this is not null, then if the field has this annotation, then do not
6374    * include in list
6375    * @param setterSet set to add fields to
6376    * @param includeAnnotation if include or exclude (or null if not looking for annotations)
6377    */
6378   @SuppressWarnings("unchecked")
6379   private static void settersHelper(Class theClass, Class superclassToStopAt, Class<?> propertyType,
6380       boolean includeSuperclassToStopAt,
6381       Class<? extends Annotation> markerAnnotation, Set<Method> setterSet, Boolean includeAnnotation) {
6382     theClass = unenhanceClass(theClass);
6383     Method[] methods = retrieveDeclaredMethods(theClass);
6384     if (length(methods) != 0) {
6385       for (Method method: methods) {
6386         //must be a getter
6387         if (!isSetter(method)) {
6388           continue;
6389         }
6390         //if checking for annotation
6391         if (markerAnnotation != null
6392             && (includeAnnotation != method.isAnnotationPresent(markerAnnotation))) {
6393           continue;
6394         }
6395         //if checking for type, and not right type, continue
6396         if (propertyType != null && !propertyType.isAssignableFrom(method.getParameterTypes()[0])) {
6397           continue;
6398         }
6399 
6400         //go for it
6401         setterSet.add(method);
6402       }
6403     }
6404     //see if done recursing (if superclassToStopAt is null, then stop at Object
6405     if (theClass.equals(superclassToStopAt) || theClass.equals(Object.class)) {
6406       return;
6407     }
6408     Class superclass = theClass.getSuperclass();
6409     if (!includeSuperclassToStopAt && superclass.equals(superclassToStopAt)) {
6410       return;
6411     }
6412     //recurse
6413     settersHelper(superclass, superclassToStopAt, propertyType,
6414         includeSuperclassToStopAt, markerAnnotation, setterSet,
6415         includeAnnotation);
6416   }
6417 
6418   /**
6419    * If this is a getter or setter, then get the property name
6420    * @param method
6421    * @return the property name
6422    */
6423   public static String propertyName(Method method) {
6424     String methodName = method.getName();
6425     boolean isGetter = methodName.startsWith("get");
6426     boolean isSetter = methodName.startsWith("set");
6427     boolean isIsser = methodName.startsWith("is");
6428     int expectedLength = isIsser ? 2 : 3;
6429     int length = methodName.length();
6430     if ((!(isGetter || isSetter || isIsser)) || (length <= expectedLength)) {
6431       throw new RuntimeException("Not a getter or setter: " + methodName);
6432     }
6433     char fourthCharLower = Character.toLowerCase(methodName.charAt(expectedLength));
6434     //if size 4, then return the string
6435     if (length == expectedLength +1) {
6436       return Character.toString(fourthCharLower);
6437     }
6438     //return the lower appended with the rest
6439     return fourthCharLower + methodName.substring(expectedLength+1, length);
6440   }
6441 
6442   /**
6443    * take a collection of beans, and go through and get all the values
6444    * of one of the javabean properties, and make a list of those values.
6445    * @param <T>
6446    * @param collection
6447    * @param propertyName
6448    * @param fieldType
6449    * @return the list
6450    */
6451   public static <T> List<T> propertyList(Collection<?> collection,
6452       String propertyName, @SuppressWarnings("unused") Class<T> fieldType) {
6453 
6454     if (collection == null) {
6455       return null;
6456     }
6457 
6458     List<T> list = new ArrayList<T>();
6459 
6460     for (Object object : collection) {
6461       T value = (T)propertyValue(object, propertyName);
6462       list.add(value);
6463     }
6464 
6465     return list;
6466 
6467   }
6468 
6469 
6470   /**
6471    * use reflection to get a property type based on getter or setter or field
6472    * @param theClass
6473    * @param propertyName
6474    * @return the property type
6475    */
6476   public static Class propertyType(Class theClass, String propertyName) {
6477     theClass = unenhanceClass(theClass);
6478     Method method = getter(theClass, propertyName, true, false);
6479     if (method != null) {
6480       return method.getReturnType();
6481     }
6482     //use setter
6483     method = setter(theClass, propertyName, true, false);
6484     if (method != null) {
6485       return method.getParameterTypes()[0];
6486     }
6487     //no setter or getter, use field
6488     Field field = field(theClass, propertyName, true, true);
6489     return field.getType();
6490   }
6491 
6492   /**
6493    * If necessary, convert an object to another type.  if type is Object.class, just return the input.
6494    * Do not convert null to an empty primitive
6495    * @param <T> is template type
6496    * @param value
6497    * @param theClass
6498    * @return the object of that instance converted into something else
6499    */
6500   public static <T> T typeCast(Object value, Class<T> theClass) {
6501     //default behavior is not to convert null to empty primitive
6502     return typeCast(value, theClass, false, false);
6503   }
6504 
6505   /**
6506    * <pre>
6507    * make a new file in the name prefix dir.  If parent dir name is c:\temp
6508    * and namePrefix is grouperDdl and nameSuffix is sql, then the file will be:
6509    *
6510    * c:\temp\grouperDdl_20080721_13_45_43_123.sql
6511    *
6512    * If the file exists, it will make a new filename, and create the empty file, and return it
6513    * </pre>
6514    *
6515    * @param parentDirName can be blank for current dir
6516    * @param namePrefix the part before the date part
6517    * @param nameSuffix the last part of file name (can contain dot or will be the extension
6518    * @param createFile true to create the file
6519    * @return the created file
6520    */
6521   public static File newFileUniqueName(String parentDirName, String namePrefix, String nameSuffix, boolean createFile) {
6522     
6523     String errorMessage = "\n\nError: grouper needs to be able to write files, make sure your directories are owned and writable by 'tomcat.tomcat' perhaps need to set this in Dockerfile if applicable"
6524         + "\nRUN chown -R tomcat:tomcat /opt/grouper/grouperWebapp\n\n";
6525     
6526     DateFormat fileNameFormat = new SimpleDateFormat("yyyyMMdd_HH_mm_ss_SSS");
6527     if (!isBlank(parentDirName)) {
6528       parentDirName=fixRelativePath(parentDirName);
6529       if (!parentDirName.endsWith("/") && !parentDirName.endsWith("\\")) {
6530         parentDirName += File.separator;
6531       }
6532 
6533       //make sure it exists and is a dir
6534       File parentDir = new File(parentDirName);
6535       if (!parentDir.exists()) {
6536         if (!parentDir.mkdirs()) {
6537           throw new RuntimeException("Cant make dir: " + parentDir.getAbsolutePath() + errorMessage);
6538         }
6539       } else {
6540         if (!parentDir.isDirectory()) {
6541           throw new RuntimeException("Parent dir is not a directory: " + parentDir.getAbsolutePath());
6542         }
6543       }
6544 
6545     } else {
6546       //make it empty string so it will concatenate well
6547       parentDirName = "";
6548     }
6549     //make sure suffix has a dot in it
6550     if (!nameSuffix.contains(".")) {
6551       nameSuffix = "." + nameSuffix;
6552     }
6553 
6554     String fileName = parentDirName + namePrefix + "_" + fileNameFormat.format(new Date()) + nameSuffix;
6555     int dotLocation = fileName.lastIndexOf('.');
6556     String fileNamePre = fileName.substring(0,dotLocation);
6557     String fileNamePost = fileName.substring(dotLocation);
6558     File theFile = new File(fileName);
6559 
6560     int i;
6561 
6562     for (i=0;i<1000;i++) {
6563 
6564       if (!theFile.exists()) {
6565         break;
6566       }
6567 
6568       fileName = fileNamePre + "_" + i + fileNamePost;
6569       theFile = new File(fileName);
6570 
6571     }
6572 
6573     if (i>=1000) {
6574       throw new RuntimeException("Cant find filename to create: " + fileName);
6575     }
6576 
6577     if (createFile) {
6578       try {
6579         if (!theFile.createNewFile()) {
6580           throw new RuntimeException("Cant create file, it returned false.  ");
6581         }
6582       } catch (Exception e) {
6583         throw new RuntimeException("Cant create file: " + fileName + ", make sure " +
6584             "permissions and such are ok, or change file location in grouper.properties if applicable.  " + errorMessage, e);
6585       }
6586     }
6587     return theFile;
6588   }
6589 
6590   /**
6591    * convert a string date into a long date (e.g. for xml export)
6592    * @param date
6593    * @return the long or null if the date was null or blank
6594    */
6595   public static Long dateLongValue(String date) {
6596     if (isBlank(date)) {
6597       return null;
6598     }
6599     Date dateObject = dateValue(date);
6600     return dateObject.getTime();
6601   }
6602 
6603   /**
6604    * web service format string
6605    */
6606   private static final String TIMESTAMP_XML_FORMAT = "yyyy/MM/dd HH:mm:ss.SSS";
6607 
6608   /**
6609    * date object to a string:
6610    * @param date
6611    * @return the long or null if the date was null or blank
6612    */
6613   public static String dateStringValue(Date date) {
6614     if (date == null) {
6615       return null;
6616     }
6617     SimpleDateFormat simpleDateFormat = new SimpleDateFormat(TIMESTAMP_XML_FORMAT);
6618     return simpleDateFormat.format(date);
6619   }
6620 
6621   /**
6622    * date object to a string:
6623    * @param theDate
6624    * @return the long or null if the date was null or blank
6625    */
6626   public static String dateStringValue(Long theDate) {
6627     if (theDate == null) {
6628       return null;
6629     }
6630     return dateStringValue(new Date(theDate));
6631   }
6632 
6633   /**
6634    * <pre>
6635    * Convert an object to a java.util.Date.  allows, dates, null, blank,
6636    * yyyymmdd or yyyymmdd hh24:mm:ss
6637    * or yyyy/MM/dd HH:mm:ss.SSS
6638    * </pre>
6639    * @param inputObject
6640    *          is the String or Date to convert
6641    *
6642    * @return the Date
6643    */
6644   public static Date dateValue(Object inputObject) {
6645     if (inputObject == null) {
6646       return null;
6647     }
6648 
6649     if (inputObject instanceof java.util.Date) {
6650       return (Date)inputObject;
6651     }
6652 
6653     if (inputObject instanceof String) {
6654       String input = (String)inputObject;
6655       //trim and handle null and empty
6656       if (isBlank(input)) {
6657         return null;
6658       }
6659 
6660       try {
6661         if (input.length() == 8) {
6662 
6663           return dateFormat().parse(input);
6664         }
6665         if (!contains(input, '.')) {
6666           if (contains(input, '/')) {
6667             return dateMinutesSecondsFormat.parse(input);
6668           }
6669           //else no slash
6670           return dateMinutesSecondsNoSlashFormat.parse(input);
6671         }
6672         if (contains(input, '/')) {
6673           //see if the period is 6 back
6674           int lastDotIndex = input.lastIndexOf('.');
6675           if (lastDotIndex == input.length() - 7) {
6676             String nonNanoInput = input.substring(0,input.length()-3);
6677             Date date = timestampFormat.parse(nonNanoInput);
6678             //get the last 3
6679             String lastThree = input.substring(input.length()-3,input.length());
6680             int lastThreeInt = Integer.parseInt(lastThree);
6681             Timestamp timestamp = new Timestamp(date.getTime());
6682             timestamp.setNanos(timestamp.getNanos() + (lastThreeInt * 1000));
6683             return timestamp;
6684           }
6685           return timestampFormat.parse(input);
6686         }
6687         //else no slash
6688         return timestampNoSlashFormat.parse(input);
6689       } catch (ParseException pe) {
6690         throw new RuntimeException(errorStart + toStringForLog(input));
6691       }
6692     }
6693 
6694     throw new RuntimeException("Cannot convert Object to date : " + toStringForLog(inputObject));
6695   }
6696 
6697   /**
6698    * match regex pattern yyyy-mm-dd or yyyy/mm/dd
6699    */
6700   private static Pattern datePattern_yyyy_mm_dd = Pattern.compile("^(\\d{4})[^\\d]+(\\d{1,2})[^\\d]+(\\d{1,2})$");
6701 
6702   /**
6703    * match regex pattern dd-mon-yyyy or dd/mon/yyyy
6704    */
6705   private static Pattern datePattern_dd_mon_yyyy = Pattern.compile("^(\\d{1,2})[^\\d]+([a-zA-Z]{3,15})[^\\d]+(\\d{4})$");
6706 
6707   /**
6708    * match regex pattern yyyy-mm-dd hh:mm:ss or yyyy/mm/dd hh:mm:ss
6709    */
6710   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})$");
6711 
6712   /**
6713    * match regex pattern dd-mon-yyyy hh:mm:ss or dd/mon/yyyy hh:mm:ss
6714    */
6715   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})$");
6716 
6717   /**
6718    * match regex pattern yyyy-mm-dd hh:mm:ss.SSS or yyyy/mm/dd hh:mm:ss.SSS
6719    */
6720   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})$");
6721 
6722   /**
6723    * match regex pattern dd-mon-yyyy hh:mm:ss.SSS or dd/mon/yyyy hh:mm:ss.SSS
6724    */
6725   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})$");
6726 
6727   /**
6728    * take as input:
6729    * yyyy/mm/dd
6730    * yyyy-mm-dd
6731    * dd-mon-yyyy
6732    * yyyy/mm/dd hh:mm:ss
6733    * dd-mon-yyyy hh:mm:ss
6734    * yyyy/mm/dd hh:mm:ss.SSS
6735    * dd-mon-yyyy hh:mm:ss.SSS
6736    * @param input
6737    * @return the date
6738    */
6739   public static Date stringToDate2(String input) {
6740 
6741     if (isBlank(input)) {
6742       return null;
6743     }
6744     input = input.trim();
6745     Matcher matcher = null;
6746 
6747     int month = 0;
6748     int day = 0;
6749     int year = 0;
6750     int hour = 0;
6751     int minute = 0;
6752     int second = 0;
6753     int milli = 0;
6754 
6755     boolean foundMatch = false;
6756 
6757     //yyyy/mm/dd
6758     if (!foundMatch) {
6759       matcher = datePattern_yyyy_mm_dd.matcher(input);
6760       if (matcher.matches()) {
6761         year = intValue(matcher.group(1));
6762         month =  intValue(matcher.group(2));
6763         day = intValue(matcher.group(3));
6764         foundMatch = true;
6765       }
6766     }
6767 
6768     //dd-mon-yyyy
6769     if (!foundMatch) {
6770       matcher = datePattern_dd_mon_yyyy.matcher(input);
6771       if (matcher.matches()) {
6772         day = intValue(matcher.group(1));
6773         month =  monthInt(matcher.group(2));
6774         year = intValue(matcher.group(3));
6775         foundMatch = true;
6776       }
6777     }
6778 
6779     //yyyy/mm/dd hh:mm:ss
6780     if (!foundMatch) {
6781       matcher = datePattern_yyyy_mm_dd_hhmmss.matcher(input);
6782       if (matcher.matches()) {
6783         year = intValue(matcher.group(1));
6784         month =  intValue(matcher.group(2));
6785         day = intValue(matcher.group(3));
6786         hour = intValue(matcher.group(4));
6787         minute = intValue(matcher.group(5));
6788         second = intValue(matcher.group(6));
6789         foundMatch = true;
6790       }
6791     }
6792 
6793     //dd-mon-yyyy hh:mm:ss
6794     if (!foundMatch) {
6795       matcher = datePattern_dd_mon_yyyy_hhmmss.matcher(input);
6796       if (matcher.matches()) {
6797         day = intValue(matcher.group(1));
6798         month =  monthInt(matcher.group(2));
6799         year = intValue(matcher.group(3));
6800         hour = intValue(matcher.group(4));
6801         minute = intValue(matcher.group(5));
6802         second = intValue(matcher.group(6));
6803         foundMatch = true;
6804       }
6805     }
6806 
6807     //yyyy/mm/dd hh:mm:ss.SSS
6808     if (!foundMatch) {
6809       matcher = datePattern_yyyy_mm_dd_hhmmss_SSS.matcher(input);
6810       if (matcher.matches()) {
6811         year = intValue(matcher.group(1));
6812         month =  intValue(matcher.group(2));
6813         day = intValue(matcher.group(3));
6814         hour = intValue(matcher.group(4));
6815         minute = intValue(matcher.group(5));
6816         second = intValue(matcher.group(6));
6817         milli = intValue(matcher.group(7));
6818         foundMatch = true;
6819       }
6820     }
6821 
6822     //dd-mon-yyyy hh:mm:ss.SSS
6823     if (!foundMatch) {
6824       matcher = datePattern_dd_mon_yyyy_hhmmss_SSS.matcher(input);
6825       if (matcher.matches()) {
6826         day = intValue(matcher.group(1));
6827         month =  monthInt(matcher.group(2));
6828         year = intValue(matcher.group(3));
6829         hour = intValue(matcher.group(4));
6830         minute = intValue(matcher.group(5));
6831         second = intValue(matcher.group(6));
6832         milli = intValue(matcher.group(7));
6833         foundMatch = true;
6834       }
6835     }
6836 
6837     Calendar calendar = Calendar.getInstance();
6838     calendar.set(Calendar.YEAR, year);
6839     calendar.set(Calendar.MONTH, month-1);
6840     calendar.set(Calendar.DAY_OF_MONTH, day);
6841     calendar.set(Calendar.HOUR_OF_DAY, hour);
6842     calendar.set(Calendar.MINUTE, minute);
6843     calendar.set(Calendar.SECOND, second);
6844     calendar.set(Calendar.MILLISECOND, milli);
6845     return calendar.getTime();
6846   }
6847 
6848   /**
6849    * convert a month string to an int (1 indexed).
6850    * e.g. if input is feb or Feb or february or February return 2
6851    * @param mon
6852    * @return the month
6853    */
6854   public static int monthInt(String mon) {
6855 
6856     if (!isBlank(mon)) {
6857       mon = mon.toLowerCase();
6858 
6859       if (equals(mon, "jan") || equals(mon, "january")) {
6860         return 1;
6861       }
6862 
6863       if (equals(mon, "feb") || equals(mon, "february")) {
6864         return 2;
6865       }
6866 
6867       if (equals(mon, "mar") || equals(mon, "march")) {
6868         return 3;
6869       }
6870 
6871       if (equals(mon, "apr") || equals(mon, "april")) {
6872         return 4;
6873       }
6874 
6875       if (equals(mon, "may")) {
6876         return 5;
6877       }
6878 
6879       if (equals(mon, "jun") || equals(mon, "june")) {
6880         return 6;
6881       }
6882 
6883       if (equals(mon, "jul") || equals(mon, "july")) {
6884         return 7;
6885       }
6886 
6887       if (equals(mon, "aug") || equals(mon, "august")) {
6888         return 8;
6889       }
6890 
6891       if (equals(mon, "sep") || equals(mon, "september")) {
6892         return 9;
6893       }
6894 
6895       if (equals(mon, "oct") || equals(mon, "october")) {
6896         return 10;
6897       }
6898 
6899       if (equals(mon, "nov") || equals(mon, "november")) {
6900         return 11;
6901       }
6902 
6903       if (equals(mon, "dec") || equals(mon, "december")) {
6904         return 12;
6905       }
6906 
6907     }
6908 
6909     throw new RuntimeException("Invalid month: " + mon);
6910   }
6911 
6912   /**
6913    * See if the input is null or if string, if it is empty or blank (whitespace)
6914    * @param input
6915    * @return true if blank
6916    */
6917   public static boolean isBlank(Object input) {
6918     if (null == input) {
6919       return true;
6920     }
6921     return (input instanceof String && isBlank((String)input));
6922   }
6923 
6924   /**
6925    * If necessary, convert an object to another type.  if type is Object.class, just return the input
6926    * @param <T> is the type to return
6927    * @param value
6928    * @param theClass
6929    * @param convertNullToDefaultPrimitive if the value is null, and theClass is primitive, should we
6930    * convert the null to a primitive default value
6931    * @param useNewInstanceHooks if theClass is not recognized, then honor the string "null", "newInstance",
6932    * or get a constructor with one param, and call it
6933    * @return the object of that instance converted into something else
6934    */
6935   @SuppressWarnings("unchecked")
6936   public static <T> T typeCast(Object value, Class<T> theClass,
6937       boolean convertNullToDefaultPrimitive, boolean useNewInstanceHooks) {
6938 
6939     if (Object.class.equals(theClass)) {
6940       return (T)value;
6941     }
6942 
6943     if (value==null) {
6944       if (convertNullToDefaultPrimitive && theClass.isPrimitive()) {
6945         if ( theClass == boolean.class ) {
6946           return (T)Boolean.FALSE;
6947         }
6948         if ( theClass == char.class ) {
6949           return (T)(Object)0;
6950         }
6951         //convert 0 to the type
6952         return typeCast(0, theClass, false, false);
6953       }
6954       return null;
6955     }
6956 
6957     if (theClass.isInstance(value)) {
6958       return (T)value;
6959     }
6960 
6961     //if array, get the base class
6962     if (theClass.isArray() && theClass.getComponentType() != null) {
6963       theClass = (Class<T>)theClass.getComponentType();
6964     }
6965     Object resultValue = null;
6966     //loop through and see the primitive types etc
6967     if (theClass.equals(Date.class)) {
6968       resultValue = dateValue(value);
6969     } else if (theClass.equals(String.class)) {
6970       resultValue = stringValue(value);
6971     } else if (theClass.equals(Timestamp.class)) {
6972       resultValue = toTimestamp(value);
6973     } else if (theClass.equals(Boolean.class) || theClass.equals(boolean.class)) {
6974       resultValue = booleanObjectValue(value);
6975     } else if (theClass.equals(Integer.class) || theClass.equals(int.class)) {
6976       resultValue = intObjectValue(value, true);
6977     } else if (theClass.equals(Double.class) || theClass.equals(double.class)) {
6978       resultValue = doubleObjectValue(value, true);
6979     } else if (theClass.equals(Float.class) || theClass.equals(float.class)) {
6980       resultValue = floatObjectValue(value, true);
6981     } else if (theClass.equals(Long.class) || theClass.equals(long.class)) {
6982       resultValue = longObjectValue(value, true);
6983     } else if (theClass.equals(Byte.class) || theClass.equals(byte.class)) {
6984       resultValue = byteObjectValue(value);
6985     } else if (theClass.equals(Character.class) || theClass.equals(char.class)) {
6986       resultValue = charObjectValue(value);
6987     } else if (theClass.equals(Short.class) || theClass.equals(short.class)) {
6988       resultValue = shortObjectValue(value);
6989     } else if ( theClass.isEnum() && (value instanceof String) ) {
6990       resultValue = Enum.valueOf((Class)theClass, (String) value);
6991     } else if ( theClass.equals(Class.class) && (value instanceof String) ) {
6992       resultValue = forName((String)value);
6993     } else if (useNewInstanceHooks && value instanceof String) {
6994       String stringValue = (String)value;
6995       if ( equals("null", stringValue)) {
6996         resultValue = null;
6997       } else if (equals("newInstance", stringValue)) {
6998         resultValue = newInstance(theClass);
6999       } else { // instantiate using string
7000         //note, we could typecast this to fit whatever is there... right now this is used for annotation
7001         try {
7002           Constructor constructor = theClass.getConstructor(new Class[] {String.class} );
7003           resultValue = constructor.newInstance(new Object[] {stringValue} );
7004         } catch (Exception e) {
7005           throw new RuntimeException("Cant find constructor with string for class: " + theClass);
7006         }
7007       }
7008     } else {
7009       throw new RuntimeException("Cannot convert from type: " + value.getClass() + " to type: " + theClass);
7010     }
7011 
7012     return (T)resultValue;
7013   }
7014 
7015   /**
7016    * see if a class is a scalar (not bean, not array or list, etc)
7017    * @param type
7018    * @return true if scalar
7019    */
7020   public static boolean isScalar(Class<?> type) {
7021 
7022     if (type.isArray()) {
7023       return false;
7024     }
7025 
7026     //definitely all primitives
7027     if (type.isPrimitive()) {
7028       return true;
7029     }
7030     //Integer, Float, etc
7031     if (Number.class.isAssignableFrom(type)) {
7032       return true;
7033     }
7034     //Date, Timestamp
7035     if (Date.class.isAssignableFrom(type)) {
7036       return true;
7037     }
7038     if (Character.class.equals(type)) {
7039       return true;
7040     }
7041     //handles strings and string builders
7042     if (CharSequence.class.equals(type) || CharSequence.class.isAssignableFrom(type)) {
7043       return true;
7044     }
7045     if (Class.class == type || Boolean.class == type || type.isEnum()) {
7046       return true;
7047     }
7048     //appears not to be a scalar
7049     return false;
7050   }
7051 
7052 
7053   /**
7054    * <pre>
7055    * Convert a string or object to a timestamp (could be string, date, timestamp, etc)
7056    * yyyymmdd
7057    * or
7058    * yyyy/MM/dd
7059    * or
7060    * yyyy/MM/dd HH:mm:ss
7061    * or
7062    * yyyy/MM/dd HH:mm:ss.SSS
7063    * or
7064    * yyyy/MM/dd HH:mm:ss.SSSSSS
7065    *
7066    * </pre>
7067    *
7068    * @param input
7069    * @return the timestamp
7070    * @throws RuntimeException if invalid format
7071    */
7072   public static Timestamp toTimestamp(Object input) {
7073 
7074     if (null == input) {
7075       return null;
7076     } else if (input instanceof java.sql.Timestamp) {
7077       return (Timestamp) input;
7078     } else if (input instanceof Long) {
7079       return new Timestamp((Long)input);
7080     } else if (input instanceof String) {
7081       return stringToTimestamp((String) input);
7082     } else if (input instanceof Date) {
7083       return new Timestamp(((Date)input).getTime());
7084     } else if (input instanceof java.sql.Date) {
7085       return new Timestamp(((java.sql.Date)input).getTime());
7086     } else {
7087       throw new RuntimeException("Cannot convert Object to timestamp : " + input);
7088     }
7089 
7090   }
7091 
7092   /**
7093    * convert an object to a string
7094    *
7095    * @param input
7096    *          is the object to convert
7097    *
7098    * @return the String conversion of the object
7099    */
7100   public static String stringValue(Object input) {
7101     //this isnt needed
7102     if (input == null || input instanceof String) {
7103       return (String) input;
7104     }
7105 
7106     if (input instanceof Timestamp) {
7107       //convert to yyyy/MM/dd HH:mm:ss.SSS
7108       return timestampToString((Timestamp) input);
7109     }
7110 
7111     if (input instanceof Date) {
7112       //convert to yyyymmdd
7113       return stringValue((Date) input);
7114     }
7115 
7116     if (input instanceof Integer || input instanceof Long) {
7117       return input.toString();
7118     }
7119     if (input instanceof Number) {
7120       DecimalFormat decimalFormat = new DecimalFormat(
7121           "###################.###############");
7122       return decimalFormat.format(((Number) input).doubleValue());
7123 
7124     }
7125 
7126     return input.toString();
7127   }
7128 
7129   /**
7130    * Convert a timestamp into a string: yyyy/MM/dd HH:mm:ss.SSS
7131    * @param timestamp
7132    * @return the string representation
7133    */
7134   public synchronized static String timestampToString(Date timestamp) {
7135     if (timestamp == null) {
7136       return null;
7137     }
7138     return timestampFormat.format(timestamp);
7139   }
7140 
7141   /**
7142    * Convert a timestamp into a string: yyyy_MM_dd__HH_mm_ss_SSS
7143    * @param timestamp
7144    * @return the string representation
7145    */
7146   public synchronized static String timestampToFileString(Date timestamp) {
7147     if (timestamp == null) {
7148       return null;
7149     }
7150     return timestampFileFormat.format(timestamp);
7151   }
7152 
7153   /**
7154    * get the timestamp format for this thread
7155    * if you call this make sure to synchronize on FastDateUtils.class
7156    * @return the timestamp format
7157    */
7158   synchronized static SimpleDateFormat dateFormat() {
7159     return dateFormat;
7160   }
7161 
7162   /**
7163    * get the timestamp format for this thread
7164    * if you call this make sure to synchronize on FastDateUtils.class
7165    * @return the timestamp format
7166    */
7167   synchronized static SimpleDateFormat dateFormat2() {
7168     return dateFormat2;
7169   }
7170 
7171   /**
7172    * convert a date to the standard string yyyymmdd
7173    * @param date
7174    * @return the string value
7175    */
7176   public static String stringValue(java.util.Date date) {
7177     synchronized (GrouperUtil.class) {
7178       if (date == null) {
7179         return null;
7180       }
7181 
7182       String theString = dateFormat().format(date);
7183 
7184       return theString;
7185     }
7186   }
7187 
7188   /**
7189    * <pre>convert a string to timestamp based on the following formats:
7190    * yyyyMMdd
7191    * yyyy/MM/dd
7192    * yyyy/MM/dd HH:mm:ss
7193    * yyyy/MM/dd HH:mm:ss.SSS
7194    * yyyy/MM/dd HH:mm:ss.SSSSSS
7195    * </pre>
7196    * @param input
7197    * @return the timestamp object
7198    */
7199   public static Timestamp stringToTimestamp(String input) {
7200     Date date = stringToTimestampHelper(input);
7201     if (date == null) {
7202       return null;
7203     }
7204     //maybe already a timestamp
7205     if (date instanceof Timestamp) {
7206       return (Timestamp)date;
7207     }
7208     return new Timestamp(date.getTime());
7209   }
7210 
7211   /**
7212    * return a date based on input, null safe.  Allow any of the three
7213    * formats:
7214    * yyyyMMdd
7215    * yyyy/MM/dd
7216    * yyyy/MM/dd HH:mm:ss
7217    * yyyy/MM/dd HH:mm:ss.SSS
7218    * yyyy/MM/dd HH:mm:ss.SSSSSS
7219    *
7220    * @param input
7221    * @return the millis, -1 for null
7222    */
7223   synchronized static Date stringToTimestampHelper(String input) {
7224     //trim and handle null and empty
7225     if (isBlank(input)) {
7226       return null;
7227     }
7228     input = input.trim();
7229     try {
7230       //convert mainframe
7231       if (equals("99999999", input)
7232           || equals("999999", input)) {
7233         input = "20991231";
7234       }
7235       if (input.length() == 8) {
7236 
7237         return dateFormat().parse(input);
7238       }
7239       if (input.length() == 10) {
7240 
7241         return dateFormat2().parse(input);
7242       }
7243       if (!contains(input, '.')) {
7244         if (contains(input, '/')) {
7245           return dateMinutesSecondsFormat.parse(input);
7246         }
7247         //else no slash
7248         return dateMinutesSecondsNoSlashFormat.parse(input);
7249       }
7250       if (contains(input, '/')) {
7251         //see if the period is 6 back
7252         int lastDotIndex = input.lastIndexOf('.');
7253         if (lastDotIndex == input.length() - 7) {
7254           String nonNanoInput = input.substring(0,input.length()-3);
7255           Date date = timestampFormat.parse(nonNanoInput);
7256           //get the last 3
7257           String lastThree = input.substring(input.length()-3,input.length());
7258           int lastThreeInt = Integer.parseInt(lastThree);
7259           Timestamp timestamp = new Timestamp(date.getTime());
7260           timestamp.setNanos(timestamp.getNanos() + (lastThreeInt * 1000));
7261           return timestamp;
7262         }
7263         return timestampFormat.parse(input);
7264       }
7265       //else no slash
7266       return timestampNoSlashFormat.parse(input);
7267     } catch (ParseException pe) {
7268       throw new RuntimeException(errorStart + input);
7269     }
7270   }
7271 
7272   /**
7273    * start of error parsing messages
7274    */
7275   private static final String errorStart = "Invalid timestamp, please use any of the formats: "
7276     + DATE_FORMAT + ", " + TIMESTAMP_FORMAT
7277     + ", " + DATE_MINUTES_SECONDS_FORMAT + ": ";
7278 
7279   /**
7280    * Convert an object to a byte, allow nulls
7281    * @param input
7282    * @return the boolean object value
7283    */
7284   public static BigDecimal bigDecimalObjectValue(Object input) {
7285     if (input instanceof BigDecimal) {
7286       return (BigDecimal)input;
7287     }
7288     if (isBlank(input)) {
7289       return null;
7290     }
7291     return BigDecimal.valueOf(doubleValue(input));
7292   }
7293 
7294   /**
7295    * Convert an object to a byte, allow nulls
7296    * @param input
7297    * @return the boolean object value
7298    */
7299   public static Byte byteObjectValue(Object input) {
7300     if (input instanceof Byte) {
7301       return (Byte)input;
7302     }
7303     if (isBlank(input)) {
7304       return null;
7305     }
7306     return Byte.valueOf(byteValue(input));
7307   }
7308 
7309   /**
7310    * convert an object to a byte
7311    * @param input
7312    * @return the byte
7313    */
7314   public static byte byteValue(Object input) {
7315     if (input instanceof String) {
7316       String string = (String)input;
7317       return Byte.parseByte(string);
7318     }
7319     if (input instanceof Number) {
7320       return ((Number)input).byteValue();
7321     }
7322     throw new RuntimeException("Cannot convert to byte: " + className(input));
7323   }
7324 
7325   /**
7326    * get the Double value of an object
7327    *
7328    * @param input
7329    *          is a number or String
7330    * @param allowNullBlank used to default to false, if true, return null if nul inputted
7331    *
7332    * @return the Double equivalent
7333    */
7334   public static Double doubleObjectValue(Object input, boolean allowNullBlank) {
7335 
7336     if (input instanceof Double) {
7337       return (Double) input;
7338     }
7339 
7340     if (allowNullBlank && isBlank(input)) {
7341       return null;
7342     }
7343 
7344     return Double.valueOf(doubleValue(input));
7345   }
7346 
7347   /**
7348    * get the double value of an object
7349    *
7350    * @param input
7351    *          is a number or String
7352    *
7353    * @return the double equivalent
7354    */
7355   public static double doubleValue(Object input) {
7356     if (input instanceof String) {
7357       String string = (String)input;
7358       return Double.parseDouble(string);
7359     }
7360     if (input instanceof Number) {
7361       return ((Number)input).doubleValue();
7362     }
7363     throw new RuntimeException("Cannot convert to double: "  + className(input));
7364   }
7365 
7366   /**
7367    * get the double value of an object, do not throw an
7368    * exception if there is an
7369    * error
7370    *
7371    * @param input
7372    *          is a number or String
7373    *
7374    * @return the double equivalent
7375    */
7376   public static double doubleValueNoError(Object input) {
7377     if (input == null || (input instanceof String
7378         && isBlank((String)input))) {
7379       return NOT_FOUND;
7380     }
7381 
7382     try {
7383       return doubleValue(input);
7384     } catch (Exception e) {
7385       //no need to log here
7386     }
7387 
7388     return NOT_FOUND;
7389   }
7390 
7391   /**
7392    * get the Float value of an object
7393    *
7394    * @param input
7395    *          is a number or String
7396    * @param allowNullBlank true if allow null or blank
7397    *
7398    * @return the Float equivalent
7399    */
7400   public static Float floatObjectValue(Object input, boolean allowNullBlank) {
7401 
7402     if (input instanceof Float) {
7403       return (Float) input;
7404     }
7405 
7406     if (allowNullBlank && isBlank(input)) {
7407       return null;
7408     }
7409     return Float.valueOf(floatValue(input));
7410   }
7411 
7412   /**
7413    * get the float value of an object
7414    *
7415    * @param input
7416    *          is a number or String
7417    *
7418    * @return the float equivalent
7419    */
7420   public static float floatValue(Object input) {
7421     if (input instanceof String) {
7422       String string = (String)input;
7423       return Float.parseFloat(string);
7424     }
7425     if (input instanceof Number) {
7426       return ((Number)input).floatValue();
7427     }
7428     throw new RuntimeException("Cannot convert to float: " + className(input));
7429   }
7430 
7431   /**
7432    * get the float value of an object, do not throw an exception if there is an
7433    * error
7434    *
7435    * @param input
7436    *          is a number or String
7437    *
7438    * @return the float equivalent
7439    */
7440   public static float floatValueNoError(Object input) {
7441     if (input == null || (input instanceof String
7442         && isBlank((String)input))) {
7443       return NOT_FOUND;
7444     }
7445     try {
7446       return floatValue(input);
7447     } catch (Exception e) {
7448       LOG.error(e);
7449     }
7450 
7451     return NOT_FOUND;
7452   }
7453 
7454   /**
7455    * get the Integer value of an object
7456    *
7457    * @param input
7458    *          is a number or String
7459    * @param allowNullBlank true if convert null or blank to null
7460    *
7461    * @return the Integer equivalent
7462    */
7463   public static Integer intObjectValue(Object input, boolean allowNullBlank) {
7464 
7465     if (input instanceof Integer) {
7466       return (Integer) input;
7467     }
7468 
7469     if (allowNullBlank && isBlank(input)) {
7470       return null;
7471     }
7472 
7473     return Integer.valueOf(intValue(input));
7474   }
7475 
7476   /**
7477    * convert an object to a int
7478    * @param input
7479    * @return the number
7480    */
7481   public static int intValue(Object input) {
7482     if (input instanceof String) {
7483       String string = (String)input;
7484       return Integer.parseInt(string);
7485     }
7486     if (input instanceof Number) {
7487       return ((Number)input).intValue();
7488     }
7489     throw new RuntimeException("Cannot convert to int: " + className(input));
7490   }
7491 
7492   /**
7493    * convert an object to a int
7494    * @param input
7495    * @param valueIfNull is if the input is null or empty, return this value
7496    * @return the number
7497    */
7498   public static int intValue(Object input, int valueIfNull) {
7499     if (input == null || "".equals(input)) {
7500       return valueIfNull;
7501     }
7502     return intObjectValue(input, false);
7503   }
7504 
7505   /**
7506    * if these are the stems to sync: a:b:c, a:b, a:d, a:b:d, then the top level are: a:b, a:d
7507    * @return
7508    */
7509   public static Set<String> stemCalculateTopLevelStems(Set<String> stems) {
7510 
7511     Set<String> topLevelStems = new TreeSet<String>();
7512     
7513     if (GrouperUtil.isBlank(stems)) {
7514       return topLevelStems;
7515     }
7516     
7517     //lets see if any are root
7518     if (stems.contains(":")) {
7519       topLevelStems.add(":");
7520       return topLevelStems;
7521     }
7522     
7523     
7524     // none of these are root stems
7525     SYNC_STEMS: 
7526     for (String stem : stems) {
7527       
7528       // loop through the current top level and two do things, remove ones that are covered here, and if this is covered, remove it
7529       CURRENT_STEM_PARENT_STEM: 
7530       for (String parentStem : GrouperUtil.findParentStemNames(stem)) {
7531         if (StringUtils.equals(":", parentStem) || StringUtils.isBlank(parentStem)) {
7532           continue CURRENT_STEM_PARENT_STEM;
7533         }
7534         
7535         if (topLevelStems.contains(parentStem)) {
7536           continue SYNC_STEMS;
7537         }
7538         
7539       }
7540     
7541       // remove any in the list which are covered by this one
7542       Iterator<String> iterator = topLevelStems.iterator();
7543       
7544       String syncFolderToAddWithColon = stem + ":";
7545       
7546       while (iterator.hasNext()) {
7547         String currentTopLevelStemToSync = iterator.next();
7548         if (currentTopLevelStemToSync.startsWith(syncFolderToAddWithColon)) {
7549           iterator.remove();
7550         }
7551       }
7552       
7553       //add it
7554       topLevelStems.add(stem);
7555       
7556     }
7557     return topLevelStems;
7558   }
7559   
7560   /**
7561    * remove stems that are children of the top level stem
7562    * @param stems
7563    */
7564   public static void stemRemoveChildStemsOfTopStem(List<Stem> stems) {
7565     
7566     if (stems == null) {
7567       return;
7568     }
7569     
7570     int stemSize = stems.size();
7571 
7572     //go through from smallest to largest
7573 
7574     // root is 0, first level is 1, second level is 2
7575     int numberOfStems = -1;
7576     int indexOfHighestStem = -1;
7577     
7578     for (int i=0;i<stemSize;i++) {
7579       
7580       Stem currentStem = stems.get(i);
7581       
7582       int currentNumberOfStems = currentStem.isRootStem() ? 0 : (StringUtils.countMatches(currentStem.getName(), ":") + 1);
7583       
7584       if (numberOfStems == -1 || currentNumberOfStems < numberOfStems) {
7585         numberOfStems = currentNumberOfStems;
7586         indexOfHighestStem = i;
7587       }
7588     }
7589     
7590     Stem highestStem = stems.get(indexOfHighestStem);
7591 
7592     String highestStemPrefix = highestStem.isRootStem() ? ":" : (highestStem.getName() + ":");
7593     
7594     Iterator<Stem> iterator = stems.iterator();
7595     while (iterator.hasNext()) {
7596       Stem currentStem = iterator.next();
7597       
7598       if (currentStem.equals(highestStem)) {
7599         continue;
7600       }
7601       
7602       if (highestStem.isRootStem() || currentStem.getName().startsWith(highestStemPrefix)) {
7603         iterator.remove();
7604       }
7605     }
7606   }
7607   
7608   /**
7609    * remove stems that are ancestor of the child stem
7610    * @param stems
7611    */
7612   public static void stemRemoveAncestorStemsOfChildStem(Collection<String> stemNames) {
7613     
7614     if (stemNames == null) {
7615       return;
7616     }
7617     
7618     Iterator<String> iterator = stemNames.iterator();
7619     while (iterator.hasNext()) {
7620       String currentStemName = iterator.next();
7621       
7622       if (currentStemName.equals(":") && stemNames.size() > 1) {
7623         iterator.remove();
7624       } else {
7625         String stemSub = currentStemName + ":";
7626         for (String stemName: stemNames) {
7627           
7628           if (stemName.startsWith(stemSub)) {
7629             iterator.remove();
7630             break;
7631           }
7632           
7633         }
7634       }
7635     }
7636   }
7637   
7638   /**
7639    * remove stems that are children of the top level stem
7640    * @param stems
7641    */
7642   public static void stemRemoveChildStemsOfTopStemName(Collection<String> stemNames) {
7643     
7644     if (stemNames == null) {
7645       return;
7646     }
7647     
7648     Iterator<String> iterator = stemNames.iterator();
7649     while (iterator.hasNext()) {
7650       String currentStemName = iterator.next();
7651       
7652       if (currentStemName.equals(":") && stemNames.size() > 1) {
7653        continue;
7654       } else {
7655         for (String stemName: stemNames) {
7656           if (stemName.equals(":")) {
7657             iterator.remove();
7658             break;
7659           } else {
7660             String stemSub = stemName + ":";
7661             if (currentStemName.startsWith(stemSub)) {
7662               iterator.remove();
7663               break;
7664             }
7665           } 
7666             
7667         }
7668       }
7669     }
7670   }
7671   
7672   /**
7673    * get the int value of an object, do not throw an exception if there is an
7674    * error
7675    *
7676    * @param input
7677    *          is a number or String
7678    *
7679    * @return the int equivalent
7680    */
7681   public static int intValueNoError(Object input) {
7682     if (input == null || (input instanceof String
7683         && isBlank((String)input))) {
7684       return NOT_FOUND;
7685     }
7686     try {
7687       return intValue(input);
7688     } catch (Exception e) {
7689       //no need to log here
7690     }
7691 
7692     return NOT_FOUND;
7693   }
7694 
7695   /** special number when a number is not found */
7696   public static final int NOT_FOUND = -999999999;
7697 
7698   /**
7699    * logger
7700    */
7701   private static final Log LOG = getLog(GrouperUtil.class);
7702 
7703   /**
7704    * The name says it all.
7705    */
7706   public static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
7707 
7708   /**
7709    * get the Long value of an object
7710    *
7711    * @param input
7712    *          is a number or String
7713    * @param allowNullBlank true if null or blank converts to null
7714    *
7715    * @return the Long equivalent
7716    */
7717   public static Long longObjectValue(Object input, boolean allowNullBlank) {
7718 
7719     if (input instanceof Long) {
7720       return (Long) input;
7721     }
7722 
7723     if (allowNullBlank && isBlank(input)) {
7724       return null;
7725     }
7726 
7727     return Long.valueOf(longValue(input));
7728   }
7729 
7730   /**
7731    * get the Timestamp value of an object
7732    *
7733    * @param input
7734    *          is a timestamp or long
7735    * @param allowNullBlank true if null or blank converts to null
7736    *
7737    * @return the Long equivalent
7738    */
7739   public static Timestamp timestampObjectValue(Object input, boolean allowNullBlank) {
7740 
7741     if (input instanceof Timestamp) {
7742       return (Timestamp) input;
7743     }
7744 
7745     if (input instanceof Long) {
7746       return new Timestamp((Long)input);
7747     }
7748 
7749     if (input instanceof Date) {
7750       return new Timestamp(((Date)input).getTime());
7751     }
7752 
7753     if (input instanceof String) {
7754       try {
7755         Date date = timestampFormat.parse((String)input);
7756         return new Timestamp(date.getTime());
7757       } catch (Exception e) {
7758         throw new RuntimeException("Invalid timestamp '" + input + "', expecting: " + TIMESTAMP_FORMAT);
7759       }
7760     }
7761 
7762     if (allowNullBlank && isBlank(input)) {
7763       return null;
7764     }
7765 
7766     throw new RuntimeException("Invalid timestamp: '" + input + "'");
7767   }
7768 
7769   /**
7770    * convert an object to a long
7771    * @param input
7772    * @return the number
7773    */
7774   public static long longValue(Object input) {
7775     if (input instanceof String) {
7776       String string = (String)input;
7777       return Long.parseLong(string);
7778     }
7779     if (input instanceof Number) {
7780       return ((Number)input).longValue();
7781     }
7782     if (input instanceof Timestamp) {
7783       return ((Timestamp)input).getTime();
7784     }
7785     throw new RuntimeException("Cannot convert to long: " + className(input));
7786   }
7787 
7788   /**
7789    * convert an object to a long
7790    * @param input
7791    * @param valueIfNull is if the input is null or empty, return this value
7792    * @return the number
7793    */
7794   public static long longValue(Object input, long valueIfNull) {
7795     if (input == null || "".equals(input)) {
7796       return valueIfNull;
7797     }
7798     return longObjectValue(input, false);
7799   }
7800 
7801   /**
7802    * get the long value of an object, do not throw an exception if there is an
7803    * error
7804    *
7805    * @param input
7806    *          is a number or String
7807    *
7808    * @return the long equivalent
7809    */
7810   public static long longValueNoError(Object input) {
7811     if (input == null || (input instanceof String
7812         && isBlank((String)input))) {
7813       return NOT_FOUND;
7814     }
7815     try {
7816       return longValue(input);
7817     } catch (Exception e) {
7818       //no need to log here
7819     }
7820 
7821     return NOT_FOUND;
7822   }
7823 
7824   /**
7825    * get the Short value of an object.  converts null or blank to null
7826    *
7827    * @param input
7828    *          is a number or String
7829    *
7830    * @return the Long equivalent
7831    */
7832   public static Short shortObjectValue(Object input) {
7833 
7834     if (input instanceof Short) {
7835       return (Short) input;
7836     }
7837 
7838     if (isBlank(input)) {
7839       return null;
7840     }
7841 
7842     return Short.valueOf(shortValue(input));
7843   }
7844 
7845   /**
7846    * convert an object to a short
7847    * @param input
7848    * @return the number
7849    */
7850   public static short shortValue(Object input) {
7851     if (input instanceof String) {
7852       String string = (String)input;
7853       return Short.parseShort(string);
7854     }
7855     if (input instanceof Number) {
7856       return ((Number)input).shortValue();
7857     }
7858     throw new RuntimeException("Cannot convert to short: " + className(input));
7859   }
7860 
7861   /**
7862    * get the Character wrapper value for the input
7863    * @param input allow null, return null
7864    * @return the Character object wrapper
7865    */
7866   public static Character charObjectValue(Object input) {
7867     if (input instanceof Character) {
7868       return (Character) input;
7869     }
7870     if (isBlank(input)) {
7871       return null;
7872     }
7873     return new Character(charValue(input));
7874   }
7875 
7876   /**
7877    * convert an object to a char
7878    * @param input
7879    * @return the number
7880    */
7881   public static char charValue(Object input) {
7882     if (input instanceof Character) {
7883       return ((Character) input).charValue();
7884     }
7885     //if string length 1, thats ok
7886     if (input instanceof String) {
7887       String inputString = (String) input;
7888       if (inputString.length() == 1) {
7889         return inputString.charAt(0);
7890       }
7891     }
7892     throw new RuntimeException("Cannot convert to char: "
7893         + (input == null ? null : (input.getClass() + ", " + input)));
7894   }
7895 
7896   /**
7897    * Create the parent directories for a file if they do not already exist
7898    * @param file
7899    */
7900   public static void createParentDirectories(File file) {
7901     if (!file.getParentFile().exists()) {
7902       if (!file.getParentFile().mkdirs()) {
7903         throw new RuntimeException("Could not create directory : " + file.getParentFile());
7904       }
7905     }
7906   }
7907 
7908   /**
7909    * save a string into a file, file does not have to exist
7910    *
7911    * @param file
7912    *          is the file to save to
7913    * @param contents
7914    *          is the contents of the file
7915    */
7916   public static void saveStringIntoFile(File file, String contents) {
7917     try {
7918       String encoding = GrouperConfig.retrieveConfig().propertyValueString("grouper.default.fileEncoding", "UTF-8");
7919 
7920       writeStringToFile(file, contents, encoding);
7921     } catch (IOException ioe) {
7922       throw new RuntimeException(ioe);
7923     }
7924   }
7925 
7926   /**
7927    * remove whitespace from string
7928    * @return new string
7929    */
7930   public static String whitespaceRemove(String input) {
7931     if (input == null) {
7932       return input;
7933     }
7934     return input.replaceAll("\\s","");
7935   }
7936   
7937   /**
7938    * normalize new lines to unix
7939    * @param input
7940    * @return new string
7941    */
7942   public static String whitespaceNormalizeNewLines(String input) {
7943     if (StringUtils.isBlank(input)) {
7944       return input;
7945     }
7946     String modifiedInput = input.replaceAll("\r\n", "\n");
7947     modifiedInput = modifiedInput.replaceAll("\r", "\n");
7948     return modifiedInput;
7949   }
7950   
7951   /**
7952    * save a string into a file, file does not have to exist
7953    *
7954    * @param file
7955    *          is the file to save to
7956    * @param contents
7957    *          is the contents of the file
7958    * @param onlyIfDifferentContents true if only saving due to different contents
7959    * @param ignoreWhitespace true to ignore whitespace
7960    * @return true if contents were saved (thus different if param set)
7961    */
7962   public static boolean saveStringIntoFile(File file, String contents,
7963       boolean onlyIfDifferentContents, boolean ignoreWhitespace) {
7964     if (onlyIfDifferentContents && file.exists()) {
7965       String fileContents = readFileIntoString(file);
7966       String compressedContents = contents;
7967       if (ignoreWhitespace) {
7968         compressedContents = replaceWhitespaceWithSpace(compressedContents);
7969         fileContents = replaceWhitespaceWithSpace(fileContents);
7970       }
7971 
7972       //they are the same, dont worry about it
7973       if (equals(fileContents, compressedContents)) {
7974         return false;
7975       }
7976 
7977     }
7978     saveStringIntoFile(file, contents);
7979     return true;
7980   }
7981 
7982   /**
7983    * <p>
7984    * Writes data to a file. The file will be created if it does not exist.
7985    * </p>
7986    * <p>
7987    * There is no readFileToString method without encoding parameter because
7988    * the default encoding can differ between platforms and therefore results
7989    * in inconsistent results.
7990    * </p>
7991    *
7992    * @param file the file to write.
7993    * @param data The content to write to the file.
7994    * @param encoding encoding to use
7995    * @throws IOException in case of an I/O error
7996    * @throws UnsupportedEncodingException if the encoding is not supported
7997    *   by the VM
7998    */
7999   public static void writeStringToFile(File file, String data, String encoding)
8000       throws IOException {
8001     OutputStream out = new java.io.FileOutputStream(file);
8002     try {
8003       out.write(data.getBytes(encoding));
8004     } finally {
8005       closeQuietly(out);
8006     }
8007   }
8008 
8009   /**
8010    * @param file
8011    *          is the file to read into a string
8012    *
8013    * @return String
8014    */
8015   public static String readFileIntoString(File file) {
8016 
8017     if (file == null) {
8018       return null;
8019     }
8020     try {
8021       return readFileToString(file, "UTF-8");
8022     } catch (IOException ioe) {
8023       throw new RuntimeException(ioe);
8024     }
8025   }
8026 
8027   /**
8028    * @param resourceName is the string resource from classpath to read (e.g. grouper.properties)
8029    * @param allowNull is true if its ok if the resource is null or if it is not found or blank or whatever.
8030    *
8031    * @return String or null if allowed or RuntimeException if not allowed
8032    */
8033   public static String readResourceIntoString(String resourceName, boolean allowNull) {
8034     if (isBlank(resourceName)) {
8035       if (allowNull) {
8036         return null;
8037       }
8038       throw new RuntimeException("Resource name is blank");
8039     }
8040     URL url = computeUrl(resourceName, allowNull);
8041 
8042     //this is ok
8043     if (url == null && allowNull) {
8044       return null;
8045     }
8046 
8047     InputStream inputStream = null;
8048     StringWriter stringWriter = new StringWriter();
8049     try {
8050       inputStream = url.openStream();
8051       String encoding = GrouperConfig.retrieveConfig().propertyValueString("grouper.default.fileEncoding", "UTF-8");
8052       copy(inputStream, stringWriter, encoding);
8053     } catch (IOException ioe) {
8054       throw new RuntimeException("Error reading resource: '" + resourceName + "'", ioe);
8055     } finally {
8056       closeQuietly(inputStream);
8057       closeQuietly(stringWriter);
8058     }
8059     return stringWriter.toString();
8060   }
8061 
8062   /**
8063    * <p>
8064    * Reads the contents of a file into a String.
8065    * </p>
8066    * <p>
8067    * There is no readFileToString method without encoding parameter because
8068    * the default encoding can differ between platforms and therefore results
8069    * in inconsistent results.
8070    * </p>
8071    *
8072    * @param file the file to read.
8073    * @param encoding the encoding to use
8074    * @return The file contents or null if read failed.
8075    * @throws IOException in case of an I/O error
8076    */
8077   public static String readFileToString(File file, String encoding) throws IOException {
8078     InputStream in = new java.io.FileInputStream(file);
8079     try {
8080       return toString(in, encoding);
8081     } finally {
8082       closeQuietly(in);
8083     }
8084   }
8085 
8086   /**
8087    * replace all whitespace with space
8088    * @param input
8089    * @return the string
8090    */
8091   public static String replaceWhitespaceWithSpace(String input) {
8092     if (input == null) {
8093       return input;
8094     }
8095     return input.replaceAll("\\s+", " ");
8096   }
8097 
8098   /**
8099    * Unconditionally close an <code>InputStream</code>.
8100    * Equivalent to {@link InputStream#close()}, except any exceptions will be ignored.
8101    * @param input A (possibly null) InputStream
8102    */
8103   public static void closeQuietly(InputStream input) {
8104     if (input == null) {
8105       return;
8106     }
8107 
8108     try {
8109       input.close();
8110     } catch (IOException ioe) {
8111     }
8112   }
8113 
8114   /**
8115    * Unconditionally close an <code>OutputStream</code>.
8116    * Equivalent to {@link OutputStream#close()}, except any exceptions will be ignored.
8117    * @param output A (possibly null) OutputStream
8118    */
8119   public static void closeQuietly(OutputStream output) {
8120     if (output == null) {
8121       return;
8122     }
8123 
8124     try {
8125       output.close();
8126     } catch (IOException ioe) {
8127     }
8128   }
8129 
8130   /**
8131    * Unconditionally close an <code>Reader</code>.
8132    * Equivalent to {@link Reader#close()}, except any exceptions will be ignored.
8133    *
8134    * @param input A (possibly null) Reader
8135    */
8136   public static void closeQuietly(Reader input) {
8137     if (input == null) {
8138       return;
8139     }
8140 
8141     try {
8142       input.close();
8143     } catch (IOException ioe) {
8144     }
8145   }
8146 
8147   /**
8148    * close a writer quietly
8149    * @param writer
8150    */
8151   public static void closeQuietly(Writer writer) {
8152     if (writer != null) {
8153       try {
8154         writer.close();
8155       } catch (IOException e) {
8156         //swallow, its ok
8157       }
8158     }
8159   }
8160 
8161   /**
8162    * close a writer quietly
8163    * @param writer
8164    */
8165   public static void closeQuietly(XMLStreamWriter writer) {
8166     if (writer != null) {
8167       try {
8168         writer.close();
8169       } catch (XMLStreamException e) {
8170         //swallow, its ok
8171       }
8172     }
8173   }
8174 
8175   /**
8176    * Get the contents of an <code>InputStream</code> as a String.
8177    * @param input the <code>InputStream</code> to read from
8178    * @param encoding The name of a supported character encoding. See the
8179    *   <a href="http://www.iana.org/assignments/character-sets">IANA
8180    *   Charset Registry</a> for a list of valid encoding types.
8181    * @return the requested <code>String</code>
8182    * @throws IOException In case of an I/O problem
8183    */
8184   public static String toString(InputStream input, String encoding) throws IOException {
8185     StringWriter sw = new StringWriter();
8186     copy(input, sw, encoding);
8187     return sw.toString();
8188   }
8189 
8190   /**
8191    * Copy and convert bytes from an <code>InputStream</code> to chars on a
8192    * <code>Writer</code>, using the specified encoding.
8193    * @param input the <code>InputStream</code> to read from
8194    * @param output the <code>Writer</code> to write to
8195    * @param encoding The name of a supported character encoding. See the
8196    * <a href="http://www.iana.org/assignments/character-sets">IANA
8197    * Charset Registry</a> for a list of valid encoding types.
8198    * @throws IOException In case of an I/O problem
8199    */
8200   public static void copy(InputStream input, Writer output, String encoding)
8201       throws IOException {
8202     InputStreamReader in = new InputStreamReader(input, encoding);
8203     copy(in, output);
8204   }
8205 
8206   /**
8207    * Copy chars from a <code>Reader</code> to a <code>Writer</code>.
8208    * @param input the <code>Reader</code> to read from
8209    * @param output the <code>Writer</code> to write to
8210    * @return the number of characters copied
8211    * @throws IOException In case of an I/O problem
8212    */
8213   public static int copy(Reader input, Writer output) throws IOException {
8214     char[] buffer = new char[DEFAULT_BUFFER_SIZE];
8215     int count = 0;
8216     int n = 0;
8217     while (-1 != (n = input.read(buffer))) {
8218       output.write(buffer, 0, n);
8219       count += n;
8220     }
8221     return count;
8222   }
8223 
8224   /**
8225    * join a thread
8226    * @param thread
8227    */
8228   public static void threadJoin(Thread thread) {
8229     if (thread == null) {
8230       return;
8231     }
8232     try {
8233       thread.join();
8234     } catch (InterruptedException ie) {
8235       throw new RuntimeException("Thread interrupted: " + thread.getName(), ie);
8236     }
8237   }
8238   
8239   /**
8240    * join a thread
8241    * @param thread
8242    * @param millis
8243    */
8244   public static void threadJoin(Thread thread, long millis) {
8245     try {
8246       thread.join(millis);
8247     } catch (InterruptedException ie) {
8248       throw new RuntimeException("Thread interrupted: " + thread.getName(), ie);
8249     }
8250   }
8251   
8252   /**
8253    * this method takes a long (less than 62) and converts it to a 1 character
8254    * string (a-z, A-Z, 0-9)
8255    *
8256    * @param theLong
8257    *          is the long (less than 62) to convert to a 1 character string
8258    *
8259    * @return a one character string
8260    */
8261   public static String convertLongToChar(long theLong) {
8262     if ((theLong < 0) || (theLong >= 62)) {
8263       throw new RuntimeException("convertLongToChar() "
8264           + " invalid input (not >=0 && <62: " + theLong);
8265     } else if (theLong < 26) {
8266       return "" + (char) ('a' + theLong);
8267     } else if (theLong < 52) {
8268       return "" + (char) ('A' + (theLong - 26));
8269     } else {
8270       return "" + (char) ('0' + (theLong - 52));
8271     }
8272   }
8273 
8274   /**
8275    * this method takes a long (less than 36) and converts it to a 1 character
8276    * string (A-Z, 0-9)
8277    *
8278    * @param theLong
8279    *          is the long (less than 36) to convert to a 1 character string
8280    *
8281    * @return a one character string
8282    */
8283   public static String convertLongToCharSmall(long theLong) {
8284     if ((theLong < 0) || (theLong >= 36)) {
8285       throw new RuntimeException("convertLongToCharSmall() "
8286           + " invalid input (not >=0 && <36: " + theLong);
8287     } else if (theLong < 26) {
8288       return "" + (char) ('A' + theLong);
8289     } else {
8290       return "" + (char) ('0' + (theLong - 26));
8291     }
8292   }
8293 
8294   /**
8295    * convert a long to a string by converting it to base 62 (26 lower, 26 upper,
8296    * 10 digits)
8297    *
8298    * @param theLong
8299    *          is the long to convert
8300    *
8301    * @return the String conversion of this
8302    */
8303   public static String convertLongToString(long theLong) {
8304     long quotient = theLong / 62;
8305     long remainder = theLong % 62;
8306 
8307     if (quotient == 0) {
8308       return convertLongToChar(remainder);
8309     }
8310     StringBuffer result = new StringBuffer();
8311     result.append(convertLongToString(quotient));
8312     result.append(convertLongToChar(remainder));
8313 
8314     return result.toString();
8315   }
8316 
8317   /**
8318    * convert a long to a string by converting it to base 36 (26 upper, 10
8319    * digits)
8320    *
8321    * @param theLong
8322    *          is the long to convert
8323    *
8324    * @return the String conversion of this
8325    */
8326   public static String convertLongToStringSmall(long theLong) {
8327     long quotient = theLong / 36;
8328     long remainder = theLong % 36;
8329 
8330     if (quotient == 0) {
8331       return convertLongToCharSmall(remainder);
8332     }
8333     StringBuffer result = new StringBuffer();
8334     result.append(convertLongToStringSmall(quotient));
8335     result.append(convertLongToCharSmall(remainder));
8336 
8337     return result.toString();
8338   }
8339 
8340   /**
8341    * increment a character (A-Z then 0-9)
8342    *
8343    * @param theChar
8344    *
8345    * @return the value
8346    */
8347   public static char incrementChar(char theChar) {
8348     if (theChar == 'Z') {
8349       return '0';
8350     }
8351 
8352     if (theChar == '9') {
8353       return 'A';
8354     }
8355 
8356     return ++theChar;
8357   }
8358 
8359   /**
8360    * Increment a string with A-Z and 0-9 (no lower case so case insensitive apps
8361    * like windows IE will still work)
8362    *
8363    * @param string
8364    *
8365    * @return the value
8366    */
8367   public static char[] incrementStringInt(char[] string) {
8368     if (string == null) {
8369       return string;
8370     }
8371 
8372     //loop through the string backwards
8373     int i = 0;
8374 
8375     for (i = string.length - 1; i >= 0; i--) {
8376       char inc = string[i];
8377       inc = incrementChar(inc);
8378       string[i] = inc;
8379 
8380       if (inc != 'A') {
8381         break;
8382       }
8383     }
8384 
8385     //if we are at 0, then it means we hit AAAAAAA (or more)
8386     if (i < 0) {
8387       return ("A" + new String(string)).toCharArray();
8388     }
8389 
8390     return string;
8391   }
8392 
8393   /**
8394    * read properties from a resource, dont modify the properties returned since they are cached
8395    * @param resourceName
8396    * @return the properties
8397    */
8398   public synchronized static Properties propertiesFromResourceName(String resourceName) {
8399     return propertiesFromResourceName(resourceName, true, true);
8400   }
8401 
8402   /**
8403    * cache properties
8404    */
8405   private static GrouperCache<File, Properties> propertiesFromFileCache = null;
8406 
8407   /**
8408    * synchronize on this object
8409    */
8410   private static Object propertiesFromFileCacheSemaphore = new Object();
8411 
8412   /**
8413    * properties from File cache
8414    * @return cache
8415    */
8416   private static GrouperCache<File, Properties> propertiesFromFileCache() {
8417     if (propertiesFromFileCache == null) {
8418       synchronized(propertiesFromFileCacheSemaphore) {
8419         if (propertiesFromFileCache == null) {
8420       propertiesFromFileCache = new GrouperCache<File,Properties>(
8421           GrouperUtil.class.getName() + ".propertiesFromFileCache", 200, false, 300, 300, false);
8422     }
8423       }
8424     }
8425     return propertiesFromFileCache;
8426   }
8427 
8428   /**
8429    * cache properties
8430    */
8431   static GrouperCache<String, Properties> propertiesFromUrlCache = null;
8432 
8433   /**
8434    * synchronize on this object
8435    */
8436   private static Object propertiesFromUrlCacheSemaphore = new Object();
8437 
8438   /**
8439    * properties from url cache
8440    * @return cache
8441    */
8442   private static GrouperCache<String, Properties> propertiesFromUrlCache() {
8443     if (propertiesFromUrlCache == null) {
8444       synchronized(propertiesFromUrlCacheSemaphore) {
8445         if (propertiesFromUrlCache == null) {
8446           propertiesFromUrlCache = new GrouperCache<String,Properties>(
8447               GrouperUtil.class.getName() + ".propertiesFromUrlCache", 200, false, 300, 300, false);
8448         }
8449       }
8450     }
8451     return propertiesFromUrlCache;
8452   }
8453 
8454   /**
8455    * cache properties
8456    */
8457   private static GrouperCache<String, Properties> propertiesFromUrlFailsafeCache = null;
8458 
8459   /**
8460    * synchronize on this object
8461    */
8462   private static Object propertiesFromUrlFailsafeCacheSemaphore = new Object();
8463 
8464   /**
8465    * properties from url failsafe cache
8466    * @return cache
8467    */
8468   private static GrouperCache<String, Properties> propertiesFromUrlFailsafeCache() {
8469     if (propertiesFromUrlFailsafeCache == null) {
8470       synchronized(propertiesFromUrlFailsafeCacheSemaphore) {
8471         if (propertiesFromUrlFailsafeCache == null) {
8472           propertiesFromUrlFailsafeCache = new GrouperCache<String,Properties>(
8473               GrouperUtil.class.getName() + ".propertiesFromUrlFailsafeCache", 200, false, 60*60*24, 60*60*24, false);
8474         }
8475       }
8476     }
8477     return propertiesFromUrlFailsafeCache;
8478   }
8479 
8480   /** variable for testing */
8481   static int propertiesFromUrlHttpCount = 0;
8482 
8483   /** variable for testing */
8484   static int propertiesFromUrlFailsafeGetCount = 0;
8485 
8486   /** variable for testing */
8487   static boolean propertiesFromUrlFailForTest = false;
8488 
8489   /**
8490    * this will get the properties from an external url.  It will cache these (failsafe),
8491    * and will escape them based on grouper's properties escaper (configurable)
8492    * @param urlString e.g. http://localhost:8090/grouper/test.properties
8493    * @param useCache if should cache for 2 minutes
8494    * @param useFailSafeCache if should use this cache for 1 day if the url cant connect
8495    * @param grouperHtmlFilter
8496    * @return the properties
8497    */
8498   public static Properties propertiesFromUrl(String urlString, boolean useCache,
8499       boolean useFailSafeCache, GrouperHtmlFilter grouperHtmlFilter) {
8500 
8501     Properties properties = null;
8502 
8503     if (useCache) {
8504       properties = propertiesFromUrlCache().get(urlString);
8505       if (properties != null) {
8506         if (useFailSafeCache) {
8507           //update this
8508           propertiesFromUrlFailsafeCache().put(urlString, properties);
8509         }
8510         return properties;
8511       }
8512     }
8513 
8514     InputStream inputStream = null;
8515     try {
8516       if (propertiesFromUrlFailForTest) {
8517         //reset
8518         propertiesFromUrlFailForTest=false;
8519         throw new RuntimeException("testing here!!!!");
8520       }
8521       URL url = new URL(urlString);
8522       properties = new Properties();
8523       inputStream = url.openConnection().getInputStream();
8524       properties.load(inputStream);
8525 
8526       //for testing
8527       propertiesFromUrlHttpCount++;
8528 
8529       if (grouperHtmlFilter != null) {
8530         for (Object key : properties.keySet()) {
8531           String value = (String)properties.get(key);
8532           String formattedValue = grouperHtmlFilter.filterHtml(value);
8533           properties.put(key, formattedValue);
8534         }
8535       }
8536 
8537     } catch (Exception e) {
8538       //failsafe means if problem, keep on keeping on
8539       if (useFailSafeCache) {
8540         properties = propertiesFromUrlFailsafeCache().get(urlString);
8541       }
8542       String error = "Problem with url: " + urlString;
8543       //always log since could have security problems with throwing exceptions
8544       LOG.error(error, e);
8545 
8546       error = "Problem with url: " + StringUtils.abbreviate(urlString, 19);
8547 
8548       if (!useFailSafeCache || properties == null) {
8549         throw new RuntimeException(error, e);
8550       }
8551       //just log if got from failsafe
8552       if (useCache) {
8553         propertiesFromUrlCache().put(urlString, properties);
8554       }
8555       //for testing
8556       propertiesFromUrlFailsafeGetCount++;
8557       //note: dont put in failsafe cache again...
8558       return properties;
8559     } finally {
8560       closeQuietly(inputStream);
8561     }
8562 
8563     //add to cache if should
8564     if (useCache) {
8565       propertiesFromUrlCache().put(urlString, properties);
8566     }
8567     if (useFailSafeCache) {
8568       //update this
8569       propertiesFromUrlFailsafeCache().put(urlString, properties);
8570     }
8571     return properties;
8572 
8573 
8574   }
8575 
8576   /**
8577    * properties from file
8578    * @param file
8579    * @param useCache
8580    * @return properties
8581    */
8582   public synchronized static Properties propertiesFromFile(File file, boolean useCache) {
8583     Properties properties = null;
8584     if (useCache) {
8585       properties = propertiesFromFileCache().get(file);
8586       if (properties != null) {
8587         return properties;
8588       }
8589     }
8590 
8591     FileInputStream fileInputStream = null;
8592 
8593     try {
8594       fileInputStream = new FileInputStream(file);
8595       properties = new Properties();
8596       properties.load(fileInputStream);
8597     } catch (IOException ioe) {
8598       throw new RuntimeException("Problem with file: " + file, ioe);
8599     } finally {
8600 
8601       closeQuietly(fileInputStream);
8602 
8603     }
8604 
8605     if (useCache) {
8606       propertiesFromFileCache().put(file, properties);
8607     }
8608     return properties;
8609   }
8610 
8611   /**
8612    * read properties from a resource, dont modify the properties returned since they are cached
8613    * @param resourceName
8614    * @param useCache
8615    * @param exceptionIfNotExist
8616    * @return the properties or null if not exist
8617    */
8618   public synchronized static Properties propertiesFromResourceName(String resourceName, boolean useCache,
8619       boolean exceptionIfNotExist) {
8620 
8621     Properties properties = resourcePropertiesCache() == null ? null : resourcePropertiesCache().get(resourceName);
8622 
8623     if (resourcePropertiesCache() == null || !useCache || resourcePropertiesCache().get(resourceName) == null) {
8624 
8625       properties = new Properties();
8626 
8627       URL url = computeUrl(resourceName, true);
8628       InputStream inputStream = null;
8629       try {
8630         inputStream = url.openStream();
8631         properties.load(inputStream);
8632       } catch (Exception e) {
8633         if (exceptionIfNotExist) {
8634           throw new RuntimeException("Problem with resource: '" + resourceName + "'", e);
8635         }
8636         properties = null;
8637       } finally {
8638         closeQuietly(inputStream);
8639 
8640         if (useCache && resourcePropertiesCache() != null) {
8641           resourcePropertiesCache().put(resourceName, properties);
8642         }
8643       }
8644     }
8645     //Hack; Gary 7th Nov 2008
8646     fixHibernateConnectionUrl(properties);
8647 
8648     return properties;
8649   }
8650 
8651   /**
8652    * do a case-insensitive matching
8653    * @param theEnumClass class of the enum
8654    * @param <E> generic type
8655    *
8656    * @param string
8657    * @param exceptionOnNotFound true if exception should be thrown on not found
8658    * @return the enum or null or exception if not found
8659    * @throws RuntimeException if there is a problem
8660    */
8661   public static <E extends Enum<?>> E enumValueOfIgnoreCase(Class<E> theEnumClass, String string,
8662       boolean exceptionOnNotFound) throws RuntimeException {
8663     return enumValueOfIgnoreCase(theEnumClass, string, exceptionOnNotFound, true);
8664   }
8665 
8666 
8667   /**
8668    * do a case-insensitive matching
8669    * @param theEnumClass class of the enum
8670    * @param <E> generic type
8671    *
8672    * @param string
8673    * @param exceptionOnNotFound true if exception should be thrown on not found
8674    * @param exceptionIfInvalid if there is a string, but it is invalid, if should throw exception
8675    * @return the enum or null or exception if not found
8676    * @throws RuntimeException if there is a problem
8677    */
8678   public static <E extends Enum<?>> E enumValueOfIgnoreCase(Class<E> theEnumClass, String string,
8679       boolean exceptionOnNotFound, boolean exceptionIfInvalid) throws RuntimeException {
8680 
8681     if (!exceptionOnNotFound && isBlank(string)) {
8682       return null;
8683     }
8684     for (E e : theEnumClass.getEnumConstants()) {
8685       if (equalsIgnoreCase(string, e.name())) {
8686         return e;
8687       }
8688     }
8689     if (!exceptionIfInvalid) {
8690       return null;
8691     }
8692     StringBuilder error = new StringBuilder(
8693         "Cant find " + theEnumClass.getSimpleName() + " from string: '").append(string);
8694     error.append("', expecting one of: ");
8695     for (E e : theEnumClass.getEnumConstants()) {
8696       error.append(e.name()).append(", ");
8697     }
8698     throw new RuntimeException(error.toString());
8699 
8700   }
8701 
8702   /**
8703    * if there is a valid accessible property descriptor, get it
8704    * @param object
8705    * @param property
8706    * @return the property descriptor
8707    */
8708   public static PropertyDescriptor retrievePropertyDescriptor(Object object, String property) {
8709     try {
8710       return PropertyUtils.getPropertyDescriptor(object, property);
8711     } catch (Exception e) {
8712     }
8713     return null;
8714   }
8715 
8716   /**
8717    * this assumes the property exists, and is a simple property
8718    * @param object
8719    * @param property
8720    * @return the value
8721    */
8722   public static Object propertyValue(Object object, String property)  {
8723     Method getter = getter(object.getClass(), property, true, true);
8724     Object result = invokeMethod(getter, object);
8725     return result;
8726   }
8727 
8728   /**
8729    * get a value (trimmed to e) from a property file
8730    * @param properties
8731    * @param key
8732    * @return the property value
8733    */
8734   public static String propertiesValue(Properties properties, String key) {
8735     return propertiesValue(properties, null, key);
8736   }
8737 
8738   /**
8739    * get a value (trimmed to e) from a property file
8740    * @param properties
8741    * @param overrideMap for testing, to override some properties values
8742    * @param key
8743    * @return the property value
8744    */
8745   public static String propertiesValue(Properties properties, Map<String, String> overrideMap, String key) {
8746     return propertiesValue(properties, overrideMap, null, key);
8747   }
8748 
8749   /**
8750    * get a value (trimmed to e) from a property file
8751    * @param properties
8752    * @param overrideMap for testing or threadlocal, to override some properties values
8753    * @param overrideMap2 for testing, to provide some properties values
8754    * @param key
8755    * @return the property value
8756    */
8757   public static String propertiesValue(Properties properties, Map<String, String> overrideMap, Map<String, String> overrideMap2, String key) {
8758     String value = overrideMap == null ? null : overrideMap.get(key);
8759     if (isBlank(value)) {
8760       value = overrideMap2 == null ? null : overrideMap2.get(key);
8761     }
8762     if (isBlank(value)) {
8763       value = properties.getProperty(key);
8764     }
8765     return trim(value);
8766   }
8767 
8768   /**
8769    * get a boolean property, or the default if cant find
8770    * @param properties
8771    * @param propertyName
8772    * @param defaultValue
8773    * @return the boolean
8774    */
8775   public static boolean propertiesValueBoolean(Properties properties,
8776       String propertyName, boolean defaultValue) {
8777     return propertiesValueBoolean(properties, null, propertyName, defaultValue);
8778   }
8779 
8780   /**
8781    * get a boolean property, or the default if cant find
8782    * @param properties
8783    * @param overrideMap for testing to override properties
8784    * @param propertyName
8785    * @param defaultValue
8786    * @return the boolean
8787    */
8788   public static boolean propertiesValueBoolean(Properties properties,
8789       Map<String, String> overrideMap, String propertyName, boolean defaultValue) {
8790     return propertiesValueBoolean(properties, overrideMap, null, propertyName, defaultValue);
8791   }
8792 
8793   /**
8794    * get a boolean property, or the default if cant find
8795    * @param properties
8796    * @param overrideMap for testing or threadlocal to override properties
8797    * @param overrideMap2 for testing or threadlocal to override properties
8798    * @param propertyName
8799    * @param defaultValue
8800    * @return the boolean
8801    */
8802   public static boolean propertiesValueBoolean(Properties properties,
8803       Map<String, String> overrideMap, Map<String, String> overrideMap2, String propertyName, boolean defaultValue) {
8804 
8805 
8806     String value = propertiesValue(properties, overrideMap, overrideMap2, propertyName);
8807     if (isBlank(value)) {
8808       return defaultValue;
8809     }
8810 
8811     if ("true".equalsIgnoreCase(value)) {
8812       return true;
8813     }
8814     if ("false".equalsIgnoreCase(value)) {
8815       return false;
8816     }
8817     if ("t".equalsIgnoreCase(value)) {
8818       return true;
8819     }
8820     if ("f".equalsIgnoreCase(value)) {
8821       return false;
8822     }
8823     throw new RuntimeException("Invalid boolean value: '" + value + "' for property: " + propertyName + " in properties file");
8824 
8825   }
8826 
8827   /**
8828    * close a connection null safe and dont throw exception
8829    * @param connection
8830    */
8831   public static void closeQuietly(Connection connection) {
8832     if (connection != null) {
8833       try {
8834         connection.close();
8835       } catch (Exception e) {
8836         //ignore
8837       }
8838     }
8839   }
8840 
8841   /**
8842    * close a session null safe and dont throw exception
8843    * @param session
8844    */
8845   public static void closeQuietly(Session session) {
8846     if (session != null) {
8847       try {
8848         session.close();
8849       } catch (Exception e) {
8850         //ignore
8851       }
8852     }
8853   }
8854 
8855   /**
8856    * close a statement null safe and dont throw exception
8857    * @param statement
8858    */
8859   public static void closeQuietly(Statement statement) {
8860     if (statement != null) {
8861       try {
8862         statement.close();
8863       } catch (Exception e) {
8864         //ignore
8865       }
8866     }
8867   }
8868 
8869   /**
8870    * close a resultSet null safe and dont throw exception
8871    * @param resultSet
8872    */
8873   public static void closeQuietly(ResultSet resultSet) {
8874     if (resultSet != null) {
8875       try {
8876         resultSet.close();
8877       } catch (Exception e) {
8878         //ignore
8879       }
8880     }
8881   }
8882 
8883   /** cache the hostname, it wont change */
8884   private static String hostname = null;
8885 
8886   /**
8887    * get the hostname of this machine
8888    * @return the hostname
8889    */
8890   public static String hostname() {
8891 
8892     if (isBlank(hostname)) {
8893 
8894       //get the hostname
8895       hostname = "unknown";
8896       try {
8897         InetAddress addr = InetAddress.getLocalHost();
8898 
8899         // Get hostname
8900         hostname = addr.getHostName();
8901       } catch (Exception e) {
8902         LOG.error("Cant find servers hostname: ", e);
8903       }
8904     }
8905 
8906     return hostname;
8907   }
8908 
8909   /**
8910    * is ascii char
8911    * @param input
8912    * @return true if ascii
8913    */
8914   public static boolean isAscii(char input) {
8915     return input < 128;
8916   }
8917 
8918   /**
8919    * find the length of ascii chars (non ascii are counted as two)
8920    * @param input
8921    * @return the length of ascii chars
8922    */
8923   public static int lengthAscii(String input) {
8924     if (input == null) {
8925       return 0;
8926     }
8927     //see what real length is
8928     int utfLength = input.length();
8929     //count how many non asciis
8930     int extras = 0;
8931     for (int i=0;i<utfLength;i++) {
8932       //keep count of non ascii chars
8933       if (!isAscii(input.charAt(i))) {
8934         extras++;
8935       }
8936     }
8937     return utfLength + extras;
8938   }
8939 
8940   /**
8941    * rollback a transaction quietly
8942    * @param transaction
8943    */
8944   public static void rollbackQuietly(Transaction transaction) {
8945     if (transaction != null && transaction.getStatus().isOneOf(TransactionStatus.ACTIVE)) {
8946       try {
8947         transaction.rollback();
8948       } catch (Exception e) {
8949         //ignore
8950       }
8951     }
8952   }
8953 
8954   /**
8955    * rollback a connection quietly
8956    * @param connection
8957    */
8958   public static void rollbackQuietly(Connection connection) {
8959     if (connection != null) {
8960       try {
8961         connection.rollback();
8962       } catch (Exception e) {
8963         //ignore
8964       }
8965     }
8966   }
8967 
8968   /**
8969    * string length
8970    * @param string
8971    * @return string length
8972    */
8973   public static int stringLength(String string) {
8974     return string == null ? 0 : string.length();
8975   }
8976 
8977   /**
8978    * close a writer quietly
8979    * @param closeable
8980    */
8981   public static void closeQuietly(Closeable closeable) {
8982     if (closeable != null) {
8983       try {
8984         closeable.close();
8985       } catch (IOException e) {
8986         //swallow, its ok
8987       }
8988     }
8989   }
8990 
8991   /**
8992    * create a new file
8993    * @param file
8994    * @return if created
8995    */
8996   public static boolean fileCreateNewFile(File file) {
8997     if (file.exists() && file.isFile()) {
8998       return false;
8999     }
9000     
9001     try {
9002       file.createNewFile();
9003     } catch (IOException ioe) {
9004       throw new RuntimeException("Cant create file: " + file.getAbsolutePath());
9005     }
9006     
9007     return true;
9008   }
9009 
9010   /**
9011    * non ascii char length
9012    */
9013   private static int nonAsciiCharLength = -1;
9014   
9015   /**
9016    * non ascii char length for the database
9017    * @return length
9018    */
9019   private static int nonAsciiCharLength() {
9020     if (nonAsciiCharLength == -1) {
9021       nonAsciiCharLength = GrouperConfig.getPropertyInt("grouper.nonAsciiCharDbBytesLength", 3);
9022     }
9023     return nonAsciiCharLength;
9024   }
9025   
9026   /**
9027    * find the length of ascii chars (non ascii are counted as three)
9028    * @param input is the string to operate on
9029    * @param requiredLength length we need the string to be
9030    * @return the length of ascii chars
9031    */
9032   public static String truncateAscii(String input, int requiredLength) {
9033     if (input == null) {
9034       return input;
9035     }
9036     //see what real length is
9037     int utfLength = input.length();
9038 
9039     //see if not worth checking
9040     if (utfLength * 2 < requiredLength) {
9041       return input;
9042     }
9043 
9044     //count how many non asciis
9045     int asciiLength = 0;
9046     for (int i=0;i<utfLength;i++) {
9047 
9048       asciiLength++;
9049 
9050       //keep count of non ascii chars
9051       if (!isAscii(input.charAt(i))) {
9052         asciiLength+=(nonAsciiCharLength()-1);
9053       }
9054 
9055       //see if we are over
9056       if (asciiLength > requiredLength) {
9057         //do not include the current char
9058         return input.substring(0,i);
9059       }
9060     }
9061     //must have fit
9062     return input;
9063   }
9064 
9065   /**
9066    * convert a subject to string safely
9067    * @param subject
9068    * @return the string value of subject (might be null)
9069    */
9070   public static String subjectToString(Subject subject) {
9071     if (subject == null) {
9072       return null;
9073     }
9074     try {
9075       if (subject instanceof GrouperSubject) {
9076         return "Subject groupName: " + subject.getName() + ", sourceId: " + subject.getSource().getId();
9077       }
9078       return "Subject id: " + subject.getId() + ", sourceId: " + subject.getSource().getId();
9079     } catch (RuntimeException e) {
9080       //might be subject not found if lazy subject
9081       return subject.toString();
9082     }
9083   }
9084 
9085   /**
9086    * if the input is a file, read string from file.  if not, or if disabled from grouper.properties, return the input
9087    * @param in
9088    * @param disableExternalFileLookup
9089    * @return the result
9090    */
9091   public static String readFromFileIfFile(String in, boolean disableExternalFileLookup) {
9092     String theIn = in;
9093     //convert both slashes to file slashes
9094     if (File.separatorChar == '/') {
9095       theIn = replace(theIn, "\\", "/");
9096     } else {
9097       theIn = replace(theIn, "/", "\\");
9098     }
9099 
9100     //see if it is a file reference
9101     if (theIn.indexOf(File.separatorChar) != -1 && disableExternalFileLookup) {
9102       //read the contents of the file into a string
9103       theIn = readFileIntoString(new File(theIn));
9104       return theIn;
9105     }
9106     return in;
9107 
9108   }
9109 
9110   /**
9111    * Create directories, throw exception if not possible.
9112    * This is will be ok if the directory already exists (will not delete it)
9113    * @param dir
9114    */
9115   public static void mkdirs(File dir) {
9116     if (!dir.exists()) {
9117       if (!dir.mkdirs()) {
9118         throw new RuntimeException("Could not create directory : " + dir.getParentFile());
9119       }
9120       return;
9121     }
9122     if (!dir.isDirectory()) {
9123       throw new RuntimeException("Should be a directory but is not: " + dir);
9124     }
9125   }
9126 
9127   /**
9128    *
9129    * @param inPath
9130    * @return string
9131    */
9132   public static String fixRelativePath(String inPath) {
9133     if(grouperHome==null || inPath.matches("^(/|\\\\|\\w:).*")) {
9134       return inPath;
9135     }
9136     String sep = "";
9137     if(!grouperHome.matches(".*?(\\\\|/)$")) {
9138       sep = File.separator;
9139     }
9140 
9141     String outPath=grouperHome + sep + inPath;
9142 
9143     return outPath;
9144   }
9145 
9146   /**
9147    *
9148    * @param props
9149    */
9150   static void fixHibernateConnectionUrl(Properties props) {
9151     String url = props.getProperty("hibernate.connection.url");
9152     if (isBlank(url)) {
9153       return;
9154     }
9155     if (!url.startsWith("jdbc:hsqldb:")) {
9156       return;
9157     }
9158     if (url.matches("^jdbc:hsqldb:(mem|hsql|res|hsql|hsqls|http|https):.*")) {
9159       return;
9160     }
9161     int spliceAt = 12;
9162     if (url.startsWith("jdbc:hsqldb:file:")) {
9163       spliceAt = 17;
9164     }
9165     String file = url.substring(spliceAt);
9166     String newUrl = url.substring(0, spliceAt) + fixRelativePath(file);
9167     props.setProperty("hibernate.connection.url", newUrl);
9168   }
9169 
9170   /**
9171    * null safe string compare
9172    * @param first
9173    * @param second
9174    * @return true if equal
9175    */
9176   public static boolean equals(String first, String second) {
9177     if (first == second) {
9178       return true;
9179     }
9180     if (first == null || second == null) {
9181       return false;
9182     }
9183     return first.equals(second);
9184   }
9185 
9186   /**
9187    * <p>Checks if a String is whitespace, empty ("") or null.</p>
9188    *
9189    * <pre>
9190    * isBlank(null)      = true
9191    * isBlank("")        = true
9192    * isBlank(" ")       = true
9193    * isBlank("bob")     = false
9194    * isBlank("  bob  ") = false
9195    * </pre>
9196    *
9197    * @param str  the String to check, may be null
9198    * @return <code>true</code> if the String is null, empty or whitespace
9199    * @since 2.0
9200    */
9201   public static boolean isBlank(String str) {
9202     int strLen;
9203     if (str == null || (strLen = str.length()) == 0) {
9204       return true;
9205     }
9206     for (int i = 0; i < strLen; i++) {
9207       if ((Character.isWhitespace(str.charAt(i)) == false)) {
9208         return false;
9209       }
9210     }
9211     return true;
9212   }
9213 
9214   /**
9215    *
9216    * @param str
9217    * @return true if not blank
9218    */
9219   public static boolean isNotBlank(String str) {
9220     return !isBlank(str);
9221   }
9222 
9223   /**
9224    * trim whitespace from string
9225    * @param str
9226    * @return trimmed string
9227    */
9228   public static String trim(String str) {
9229     return str == null ? null : str.trim();
9230   }
9231 
9232   /**
9233    * equalsignorecase
9234    * @param str1
9235    * @param str2
9236    * @return true if the strings are equal ignore case
9237    */
9238   public static boolean equalsIgnoreCase(String str1, String str2) {
9239     return str1 == null ? str2 == null : str1.equalsIgnoreCase(str2);
9240   }
9241 
9242   /**
9243    * trim to empty, convert null to empty
9244    * @param str
9245    * @return trimmed
9246    */
9247   public static String trimToEmpty(String str) {
9248     return str == null ? "" : str.trim();
9249   }
9250 
9251   /**
9252    * <p>Abbreviates a String using ellipses. This will turn
9253    * "Now is the time for all good men" into "Now is the time for..."</p>
9254    *
9255    * <p>Specifically:
9256    * <ul>
9257    *   <li>If <code>str</code> is less than <code>maxWidth</code> characters
9258    *       long, return it.</li>
9259    *   <li>Else abbreviate it to <code>(substring(str, 0, max-3) + "...")</code>.</li>
9260    *   <li>If <code>maxWidth</code> is less than <code>4</code>, throw an
9261    *       <code>IllegalArgumentException</code>.</li>
9262    *   <li>In no case will it return a String of length greater than
9263    *       <code>maxWidth</code>.</li>
9264    * </ul>
9265    * </p>
9266    *
9267    * <pre>
9268    * StringUtils.abbreviate(null, *)      = null
9269    * StringUtils.abbreviate("", 4)        = ""
9270    * StringUtils.abbreviate("abcdefg", 6) = "abc..."
9271    * StringUtils.abbreviate("abcdefg", 7) = "abcdefg"
9272    * StringUtils.abbreviate("abcdefg", 8) = "abcdefg"
9273    * StringUtils.abbreviate("abcdefg", 4) = "a..."
9274    * StringUtils.abbreviate("abcdefg", 3) = IllegalArgumentException
9275    * </pre>
9276    *
9277    * @param str  the String to check, may be null
9278    * @param maxWidth  maximum length of result String, must be at least 4
9279    * @return abbreviated String, <code>null</code> if null String input
9280    * @throws IllegalArgumentException if the width is too small
9281    * @since 2.0
9282    */
9283   public static String abbreviate(String str, int maxWidth) {
9284     return abbreviate(str, 0, maxWidth);
9285   }
9286 
9287   /**
9288    * <p>Abbreviates a String using ellipses. This will turn
9289    * "Now is the time for all good men" into "...is the time for..."</p>
9290    *
9291    * <p>Works like <code>abbreviate(String, int)</code>, but allows you to specify
9292    * a "left edge" offset.  Note that this left edge is not necessarily going to
9293    * be the leftmost character in the result, or the first character following the
9294    * ellipses, but it will appear somewhere in the result.
9295    *
9296    * <p>In no case will it return a String of length greater than
9297    * <code>maxWidth</code>.</p>
9298    *
9299    * <pre>
9300    * StringUtils.abbreviate(null, *, *)                = null
9301    * StringUtils.abbreviate("", 0, 4)                  = ""
9302    * StringUtils.abbreviate("abcdefghijklmno", -1, 10) = "abcdefg..."
9303    * StringUtils.abbreviate("abcdefghijklmno", 0, 10)  = "abcdefg..."
9304    * StringUtils.abbreviate("abcdefghijklmno", 1, 10)  = "abcdefg..."
9305    * StringUtils.abbreviate("abcdefghijklmno", 4, 10)  = "abcdefg..."
9306    * StringUtils.abbreviate("abcdefghijklmno", 5, 10)  = "...fghi..."
9307    * StringUtils.abbreviate("abcdefghijklmno", 6, 10)  = "...ghij..."
9308    * StringUtils.abbreviate("abcdefghijklmno", 8, 10)  = "...ijklmno"
9309    * StringUtils.abbreviate("abcdefghijklmno", 10, 10) = "...ijklmno"
9310    * StringUtils.abbreviate("abcdefghijklmno", 12, 10) = "...ijklmno"
9311    * StringUtils.abbreviate("abcdefghij", 0, 3)        = IllegalArgumentException
9312    * StringUtils.abbreviate("abcdefghij", 5, 6)        = IllegalArgumentException
9313    * </pre>
9314    *
9315    * @param str  the String to check, may be null
9316    * @param offset  left edge of source String
9317    * @param maxWidth  maximum length of result String, must be at least 4
9318    * @return abbreviated String, <code>null</code> if null String input
9319    * @throws IllegalArgumentException if the width is too small
9320    * @since 2.0
9321    */
9322   public static String abbreviate(String str, int offset, int maxWidth) {
9323     if (str == null) {
9324       return null;
9325     }
9326     if (maxWidth < 4) {
9327       throw new IllegalArgumentException("Minimum abbreviation width is 4");
9328     }
9329     if (str.length() <= maxWidth) {
9330       return str;
9331     }
9332     if (offset > str.length()) {
9333       offset = str.length();
9334     }
9335     if ((str.length() - offset) < (maxWidth - 3)) {
9336       offset = str.length() - (maxWidth - 3);
9337     }
9338     if (offset <= 4) {
9339       return str.substring(0, maxWidth - 3) + "...";
9340     }
9341     if (maxWidth < 7) {
9342       throw new IllegalArgumentException("Minimum abbreviation width with offset is 7");
9343     }
9344     if ((offset + (maxWidth - 3)) < str.length()) {
9345       return "..." + abbreviate(str.substring(offset), maxWidth - 3);
9346     }
9347     return "..." + str.substring(str.length() - (maxWidth - 3));
9348   }
9349 
9350   // Splitting
9351   //-----------------------------------------------------------------------
9352   /**
9353    * <p>Splits the provided text into an array, using whitespace as the
9354    * separator.
9355    * Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
9356    *
9357    * <p>The separator is not included in the returned String array.
9358    * Adjacent separators are treated as one separator.
9359    * For more control over the split use the StrTokenizer class.</p>
9360    *
9361    * <p>A <code>null</code> input String returns <code>null</code>.</p>
9362    *
9363    * <pre>
9364    * StringUtils.split(null)       = null
9365    * StringUtils.split("")         = []
9366    * StringUtils.split("abc def")  = ["abc", "def"]
9367    * StringUtils.split("abc  def") = ["abc", "def"]
9368    * StringUtils.split(" abc ")    = ["abc"]
9369    * </pre>
9370    *
9371    * @param str  the String to parse, may be null
9372    * @return an array of parsed Strings, <code>null</code> if null String input
9373    */
9374   public static String[] split(String str) {
9375     return split(str, null, -1);
9376   }
9377 
9378   /**
9379    * <p>Splits the provided text into an array, separator specified.
9380    * This is an alternative to using StringTokenizer.</p>
9381    *
9382    * <p>The separator is not included in the returned String array.
9383    * Adjacent separators are treated as one separator.
9384    * For more control over the split use the StrTokenizer class.</p>
9385    *
9386    * <p>A <code>null</code> input String returns <code>null</code>.</p>
9387    *
9388    * <pre>
9389    * StringUtils.split(null, *)         = null
9390    * StringUtils.split("", *)           = []
9391    * StringUtils.split("a.b.c", '.')    = ["a", "b", "c"]
9392    * StringUtils.split("a..b.c", '.')   = ["a", "b", "c"]
9393    * StringUtils.split("a:b:c", '.')    = ["a:b:c"]
9394    * StringUtils.split("a\tb\nc", null) = ["a", "b", "c"]
9395    * StringUtils.split("a b c", ' ')    = ["a", "b", "c"]
9396    * </pre>
9397    *
9398    * @param str  the String to parse, may be null
9399    * @param separatorChar  the character used as the delimiter,
9400    *  <code>null</code> splits on whitespace
9401    * @return an array of parsed Strings, <code>null</code> if null String input
9402    * @since 2.0
9403    */
9404   public static String[] split(String str, char separatorChar) {
9405     return splitWorker(str, separatorChar, false);
9406   }
9407 
9408   /**
9409    * <p>Splits the provided text into an array, separators specified.
9410    * This is an alternative to using StringTokenizer.</p>
9411    *
9412    * <p>The separator is not included in the returned String array.
9413    * Adjacent separators are treated as one separator.
9414    * For more control over the split use the StrTokenizer class.</p>
9415    *
9416    * <p>A <code>null</code> input String returns <code>null</code>.
9417    * A <code>null</code> separatorChars splits on whitespace.</p>
9418    *
9419    * <pre>
9420    * StringUtils.split(null, *)         = null
9421    * StringUtils.split("", *)           = []
9422    * StringUtils.split("abc def", null) = ["abc", "def"]
9423    * StringUtils.split("abc def", " ")  = ["abc", "def"]
9424    * StringUtils.split("abc  def", " ") = ["abc", "def"]
9425    * StringUtils.split("ab:cd:ef", ":") = ["ab", "cd", "ef"]
9426    * </pre>
9427    *
9428    * @param str  the String to parse, may be null
9429    * @param separatorChars  the characters used as the delimiters,
9430    *  <code>null</code> splits on whitespace
9431    * @return an array of parsed Strings, <code>null</code> if null String input
9432    */
9433   public static String[] split(String str, String separatorChars) {
9434     return splitWorker(str, separatorChars, -1, false);
9435   }
9436 
9437   /**
9438    * <p>Splits the provided text into an array with a maximum length,
9439    * separators specified.</p>
9440    *
9441    * <p>The separator is not included in the returned String array.
9442    * Adjacent separators are treated as one separator.</p>
9443    *
9444    * <p>A <code>null</code> input String returns <code>null</code>.
9445    * A <code>null</code> separatorChars splits on whitespace.</p>
9446    *
9447    * <p>If more than <code>max</code> delimited substrings are found, the last
9448    * returned string includes all characters after the first <code>max - 1</code>
9449    * returned strings (including separator characters).</p>
9450    *
9451    * <pre>
9452    * StringUtils.split(null, *, *)            = null
9453    * StringUtils.split("", *, *)              = []
9454    * StringUtils.split("ab de fg", null, 0)   = ["ab", "cd", "ef"]
9455    * StringUtils.split("ab   de fg", null, 0) = ["ab", "cd", "ef"]
9456    * StringUtils.split("ab:cd:ef", ":", 0)    = ["ab", "cd", "ef"]
9457    * StringUtils.split("ab:cd:ef", ":", 2)    = ["ab", "cd:ef"]
9458    * </pre>
9459    *
9460    * @param str  the String to parse, may be null
9461    * @param separatorChars  the characters used as the delimiters,
9462    *  <code>null</code> splits on whitespace
9463    * @param max  the maximum number of elements to include in the
9464    *  array. A zero or negative value implies no limit
9465    * @return an array of parsed Strings, <code>null</code> if null String input
9466    */
9467   public static String[] split(String str, String separatorChars, int max) {
9468     return splitWorker(str, separatorChars, max, false);
9469   }
9470 
9471   /**
9472    * <p>Splits the provided text into an array, separator string specified.</p>
9473    *
9474    * <p>The separator(s) will not be included in the returned String array.
9475    * Adjacent separators are treated as one separator.</p>
9476    *
9477    * <p>A <code>null</code> input String returns <code>null</code>.
9478    * A <code>null</code> separator splits on whitespace.</p>
9479    *
9480    * <pre>
9481    * StringUtils.split(null, *)            = null
9482    * StringUtils.split("", *)              = []
9483    * StringUtils.split("ab de fg", null)   = ["ab", "de", "fg"]
9484    * StringUtils.split("ab   de fg", null) = ["ab", "de", "fg"]
9485    * StringUtils.split("ab:cd:ef", ":")    = ["ab", "cd", "ef"]
9486    * StringUtils.split("abstemiouslyaeiouyabstemiously", "aeiouy")  = ["bst", "m", "sl", "bst", "m", "sl"]
9487    * StringUtils.split("abstemiouslyaeiouyabstemiously", "aeiouy")  = ["abstemiously", "abstemiously"]
9488    * </pre>
9489    *
9490    * @param str  the String to parse, may be null
9491    * @param separator  String containing the String to be used as a delimiter,
9492    *  <code>null</code> splits on whitespace
9493    * @return an array of parsed Strings, <code>null</code> if null String was input
9494    */
9495   public static String[] splitByWholeSeparator(String str, String separator) {
9496     return splitByWholeSeparator(str, separator, -1);
9497   }
9498 
9499   /**
9500    * <p>Splits the provided text into an array, separator string specified.
9501    * Returns a maximum of <code>max</code> substrings.</p>
9502    *
9503    * <p>The separator(s) will not be included in the returned String array.
9504    * Adjacent separators are treated as one separator.</p>
9505    *
9506    * <p>A <code>null</code> input String returns <code>null</code>.
9507    * A <code>null</code> separator splits on whitespace.</p>
9508    *
9509    * <pre>
9510    * StringUtils.splitByWholeSeparator(null, *, *)               = null
9511    * StringUtils.splitByWholeSeparator("", *, *)                 = []
9512    * StringUtils.splitByWholeSeparator("ab de fg", null, 0)      = ["ab", "de", "fg"]
9513    * StringUtils.splitByWholeSeparator("ab   de fg", null, 0)    = ["ab", "de", "fg"]
9514    * StringUtils.splitByWholeSeparator("ab:cd:ef", ":", 2)       = ["ab", "cd"]
9515    * StringUtils.splitByWholeSeparator("abstemiouslyaeiouyabstemiously", "aeiouy", 2) = ["bst", "m"]
9516    * StringUtils.splitByWholeSeparator("abstemiouslyaeiouyabstemiously", "aeiouy", 2)  = ["abstemiously", "abstemiously"]
9517    * </pre>
9518    *
9519    * @param str  the String to parse, may be null
9520    * @param separator  String containing the String to be used as a delimiter,
9521    *  <code>null</code> splits on whitespace
9522    * @param max  the maximum number of elements to include in the returned
9523    *  array. A zero or negative value implies no limit.
9524    * @return an array of parsed Strings, <code>null</code> if null String was input
9525    */
9526   public static String[] splitByWholeSeparator(String str, String separator, int max) {
9527     if (str == null) {
9528       return null;
9529     }
9530 
9531     int len = str.length();
9532 
9533     if (len == 0) {
9534       return EMPTY_STRING_ARRAY;
9535     }
9536 
9537     if ((separator == null) || ("".equals(separator))) {
9538       // Split on whitespace.
9539       return split(str, null, max);
9540     }
9541 
9542     int separatorLength = separator.length();
9543 
9544     ArrayList substrings = new ArrayList();
9545     int numberOfSubstrings = 0;
9546     int beg = 0;
9547     int end = 0;
9548     while (end < len) {
9549       end = str.indexOf(separator, beg);
9550 
9551       if (end > -1) {
9552         if (end > beg) {
9553           numberOfSubstrings += 1;
9554 
9555           if (numberOfSubstrings == max) {
9556             end = len;
9557             substrings.add(str.substring(beg));
9558           } else {
9559             // The following is OK, because String.substring( beg, end ) excludes
9560             // the character at the position 'end'.
9561             substrings.add(str.substring(beg, end));
9562 
9563             // Set the starting point for the next search.
9564             // The following is equivalent to beg = end + (separatorLength - 1) + 1,
9565             // which is the right calculation:
9566             beg = end + separatorLength;
9567           }
9568         } else {
9569           // We found a consecutive occurrence of the separator, so skip it.
9570           beg = end + separatorLength;
9571         }
9572       } else {
9573         // String.substring( beg ) goes from 'beg' to the end of the String.
9574         substrings.add(str.substring(beg));
9575         end = len;
9576       }
9577     }
9578 
9579     return (String[]) substrings.toArray(new String[substrings.size()]);
9580   }
9581 
9582   //-----------------------------------------------------------------------
9583   /**
9584    * <p>Splits the provided text into an array, using whitespace as the
9585    * separator, preserving all tokens, including empty tokens created by
9586    * adjacent separators. This is an alternative to using StringTokenizer.
9587    * Whitespace is defined by {@link Character#isWhitespace(char)}.</p>
9588    *
9589    * <p>The separator is not included in the returned String array.
9590    * Adjacent separators are treated as separators for empty tokens.
9591    * For more control over the split use the StrTokenizer class.</p>
9592    *
9593    * <p>A <code>null</code> input String returns <code>null</code>.</p>
9594    *
9595    * <pre>
9596    * StringUtils.splitPreserveAllTokens(null)       = null
9597    * StringUtils.splitPreserveAllTokens("")         = []
9598    * StringUtils.splitPreserveAllTokens("abc def")  = ["abc", "def"]
9599    * StringUtils.splitPreserveAllTokens("abc  def") = ["abc", "", "def"]
9600    * StringUtils.splitPreserveAllTokens(" abc ")    = ["", "abc", ""]
9601    * </pre>
9602    *
9603    * @param str  the String to parse, may be <code>null</code>
9604    * @return an array of parsed Strings, <code>null</code> if null String input
9605    * @since 2.1
9606    */
9607   public static String[] splitPreserveAllTokens(String str) {
9608     return splitWorker(str, null, -1, true);
9609   }
9610 
9611   /**
9612    * <p>Splits the provided text into an array, separator specified,
9613    * preserving all tokens, including empty tokens created by adjacent
9614    * separators. This is an alternative to using StringTokenizer.</p>
9615    *
9616    * <p>The separator is not included in the returned String array.
9617    * Adjacent separators are treated as separators for empty tokens.
9618    * For more control over the split use the StrTokenizer class.</p>
9619    *
9620    * <p>A <code>null</code> input String returns <code>null</code>.</p>
9621    *
9622    * <pre>
9623    * StringUtils.splitPreserveAllTokens(null, *)         = null
9624    * StringUtils.splitPreserveAllTokens("", *)           = []
9625    * StringUtils.splitPreserveAllTokens("a.b.c", '.')    = ["a", "b", "c"]
9626    * StringUtils.splitPreserveAllTokens("a..b.c", '.')   = ["a", "b", "c"]
9627    * StringUtils.splitPreserveAllTokens("a:b:c", '.')    = ["a:b:c"]
9628    * StringUtils.splitPreserveAllTokens("a\tb\nc", null) = ["a", "b", "c"]
9629    * StringUtils.splitPreserveAllTokens("a b c", ' ')    = ["a", "b", "c"]
9630    * StringUtils.splitPreserveAllTokens("a b c ", ' ')   = ["a", "b", "c", ""]
9631    * StringUtils.splitPreserveAllTokens("a b c ", ' ')   = ["a", "b", "c", "", ""]
9632    * StringUtils.splitPreserveAllTokens(" a b c", ' ')   = ["", a", "b", "c"]
9633    * StringUtils.splitPreserveAllTokens("  a b c", ' ')  = ["", "", a", "b", "c"]
9634    * StringUtils.splitPreserveAllTokens(" a b c ", ' ')  = ["", a", "b", "c", ""]
9635    * </pre>
9636    *
9637    * @param str  the String to parse, may be <code>null</code>
9638    * @param separatorChar  the character used as the delimiter,
9639    *  <code>null</code> splits on whitespace
9640    * @return an array of parsed Strings, <code>null</code> if null String input
9641    * @since 2.1
9642    */
9643   public static String[] splitPreserveAllTokens(String str, char separatorChar) {
9644     return splitWorker(str, separatorChar, true);
9645   }
9646 
9647   /**
9648    * Performs the logic for the <code>split</code> and
9649    * <code>splitPreserveAllTokens</code> methods that do not return a
9650    * maximum array length.
9651    *
9652    * @param str  the String to parse, may be <code>null</code>
9653    * @param separatorChar the separate character
9654    * @param preserveAllTokens if <code>true</code>, adjacent separators are
9655    * treated as empty token separators; if <code>false</code>, adjacent
9656    * separators are treated as one separator.
9657    * @return an array of parsed Strings, <code>null</code> if null String input
9658    */
9659   private static String[] splitWorker(String str, char separatorChar,
9660       boolean preserveAllTokens) {
9661     // Performance tuned for 2.0 (JDK1.4)
9662 
9663     if (str == null) {
9664       return null;
9665     }
9666     int len = str.length();
9667     if (len == 0) {
9668       return EMPTY_STRING_ARRAY;
9669     }
9670     List list = new ArrayList();
9671     int i = 0, start = 0;
9672     boolean match = false;
9673     boolean lastMatch = false;
9674     while (i < len) {
9675       if (str.charAt(i) == separatorChar) {
9676         if (match || preserveAllTokens) {
9677           list.add(str.substring(start, i));
9678           match = false;
9679           lastMatch = true;
9680         }
9681         start = ++i;
9682         continue;
9683       }
9684       lastMatch = false;
9685       match = true;
9686       i++;
9687     }
9688     if (match || (preserveAllTokens && lastMatch)) {
9689       list.add(str.substring(start, i));
9690     }
9691     return (String[]) list.toArray(new String[list.size()]);
9692   }
9693 
9694   /**
9695    * <p>Splits the provided text into an array, separators specified,
9696    * preserving all tokens, including empty tokens created by adjacent
9697    * separators. This is an alternative to using StringTokenizer.</p>
9698    *
9699    * <p>The separator is not included in the returned String array.
9700    * Adjacent separators are treated as separators for empty tokens.
9701    * For more control over the split use the StrTokenizer class.</p>
9702    *
9703    * <p>A <code>null</code> input String returns <code>null</code>.
9704    * A <code>null</code> separatorChars splits on whitespace.</p>
9705    *
9706    * <pre>
9707    * StringUtils.splitPreserveAllTokens(null, *)           = null
9708    * StringUtils.splitPreserveAllTokens("", *)             = []
9709    * StringUtils.splitPreserveAllTokens("abc def", null)   = ["abc", "def"]
9710    * StringUtils.splitPreserveAllTokens("abc def", " ")    = ["abc", "def"]
9711    * StringUtils.splitPreserveAllTokens("abc  def", " ")   = ["abc", "", def"]
9712    * StringUtils.splitPreserveAllTokens("ab:cd:ef", ":")   = ["ab", "cd", "ef"]
9713    * StringUtils.splitPreserveAllTokens("ab:cd:ef:", ":")  = ["ab", "cd", "ef", ""]
9714    * StringUtils.splitPreserveAllTokens("ab:cd:ef::", ":") = ["ab", "cd", "ef", "", ""]
9715    * StringUtils.splitPreserveAllTokens("ab::cd:ef", ":")  = ["ab", "", cd", "ef"]
9716    * StringUtils.splitPreserveAllTokens(":cd:ef", ":")     = ["", cd", "ef"]
9717    * StringUtils.splitPreserveAllTokens("::cd:ef", ":")    = ["", "", cd", "ef"]
9718    * StringUtils.splitPreserveAllTokens(":cd:ef:", ":")    = ["", cd", "ef", ""]
9719    * </pre>
9720    *
9721    * @param str  the String to parse, may be <code>null</code>
9722    * @param separatorChars  the characters used as the delimiters,
9723    *  <code>null</code> splits on whitespace
9724    * @return an array of parsed Strings, <code>null</code> if null String input
9725    * @since 2.1
9726    */
9727   public static String[] splitPreserveAllTokens(String str, String separatorChars) {
9728     return splitWorker(str, separatorChars, -1, true);
9729   }
9730 
9731   /**
9732    * <p>Splits the provided text into an array with a maximum length,
9733    * separators specified, preserving all tokens, including empty tokens
9734    * created by adjacent separators.</p>
9735    *
9736    * <p>The separator is not included in the returned String array.
9737    * Adjacent separators are treated as separators for empty tokens.
9738    * Adjacent separators are treated as one separator.</p>
9739    *
9740    * <p>A <code>null</code> input String returns <code>null</code>.
9741    * A <code>null</code> separatorChars splits on whitespace.</p>
9742    *
9743    * <p>If more than <code>max</code> delimited substrings are found, the last
9744    * returned string includes all characters after the first <code>max - 1</code>
9745    * returned strings (including separator characters).</p>
9746    *
9747    * <pre>
9748    * StringUtils.splitPreserveAllTokens(null, *, *)            = null
9749    * StringUtils.splitPreserveAllTokens("", *, *)              = []
9750    * StringUtils.splitPreserveAllTokens("ab de fg", null, 0)   = ["ab", "cd", "ef"]
9751    * StringUtils.splitPreserveAllTokens("ab   de fg", null, 0) = ["ab", "cd", "ef"]
9752    * StringUtils.splitPreserveAllTokens("ab:cd:ef", ":", 0)    = ["ab", "cd", "ef"]
9753    * StringUtils.splitPreserveAllTokens("ab:cd:ef", ":", 2)    = ["ab", "cd:ef"]
9754    * StringUtils.splitPreserveAllTokens("ab   de fg", null, 2) = ["ab", "  de fg"]
9755    * StringUtils.splitPreserveAllTokens("ab   de fg", null, 3) = ["ab", "", " de fg"]
9756    * StringUtils.splitPreserveAllTokens("ab   de fg", null, 4) = ["ab", "", "", "de fg"]
9757    * </pre>
9758    *
9759    * @param str  the String to parse, may be <code>null</code>
9760    * @param separatorChars  the characters used as the delimiters,
9761    *  <code>null</code> splits on whitespace
9762    * @param max  the maximum number of elements to include in the
9763    *  array. A zero or negative value implies no limit
9764    * @return an array of parsed Strings, <code>null</code> if null String input
9765    * @since 2.1
9766    */
9767   public static String[] splitPreserveAllTokens(String str, String separatorChars, int max) {
9768     return splitWorker(str, separatorChars, max, true);
9769   }
9770 
9771   /**
9772    * Performs the logic for the <code>split</code> and
9773    * <code>splitPreserveAllTokens</code> methods that return a maximum array
9774    * length.
9775    *
9776    * @param str  the String to parse, may be <code>null</code>
9777    * @param separatorChars the separate character
9778    * @param max  the maximum number of elements to include in the
9779    *  array. A zero or negative value implies no limit.
9780    * @param preserveAllTokens if <code>true</code>, adjacent separators are
9781    * treated as empty token separators; if <code>false</code>, adjacent
9782    * separators are treated as one separator.
9783    * @return an array of parsed Strings, <code>null</code> if null String input
9784    */
9785   private static String[] splitWorker(String str, String separatorChars, int max,
9786       boolean preserveAllTokens) {
9787     // Performance tuned for 2.0 (JDK1.4)
9788     // Direct code is quicker than StringTokenizer.
9789     // Also, StringTokenizer uses isSpace() not isWhitespace()
9790 
9791     if (str == null) {
9792       return null;
9793     }
9794     int len = str.length();
9795     if (len == 0) {
9796       return EMPTY_STRING_ARRAY;
9797     }
9798     List list = new ArrayList();
9799     int sizePlus1 = 1;
9800     int i = 0, start = 0;
9801     boolean match = false;
9802     boolean lastMatch = false;
9803     if (separatorChars == null) {
9804       // Null separator means use whitespace
9805       while (i < len) {
9806         if (Character.isWhitespace(str.charAt(i))) {
9807           if (match || preserveAllTokens) {
9808             lastMatch = true;
9809             if (sizePlus1++ == max) {
9810               i = len;
9811               lastMatch = false;
9812             }
9813             list.add(str.substring(start, i));
9814             match = false;
9815           }
9816           start = ++i;
9817           continue;
9818         }
9819         lastMatch = false;
9820         match = true;
9821         i++;
9822       }
9823     } else if (separatorChars.length() == 1) {
9824       // Optimise 1 character case
9825       char sep = separatorChars.charAt(0);
9826       while (i < len) {
9827         if (str.charAt(i) == sep) {
9828           if (match || preserveAllTokens) {
9829             lastMatch = true;
9830             if (sizePlus1++ == max) {
9831               i = len;
9832               lastMatch = false;
9833             }
9834             list.add(str.substring(start, i));
9835             match = false;
9836           }
9837           start = ++i;
9838           continue;
9839         }
9840         lastMatch = false;
9841         match = true;
9842         i++;
9843       }
9844     } else {
9845       // standard case
9846       while (i < len) {
9847         if (separatorChars.indexOf(str.charAt(i)) >= 0) {
9848           if (match || preserveAllTokens) {
9849             lastMatch = true;
9850             if (sizePlus1++ == max) {
9851               i = len;
9852               lastMatch = false;
9853             }
9854             list.add(str.substring(start, i));
9855             match = false;
9856           }
9857           start = ++i;
9858           continue;
9859         }
9860         lastMatch = false;
9861         match = true;
9862         i++;
9863       }
9864     }
9865     if (match || (preserveAllTokens && lastMatch)) {
9866       list.add(str.substring(start, i));
9867     }
9868     return (String[]) list.toArray(new String[list.size()]);
9869   }
9870 
9871   /**
9872    * <p>Joins the elements of the provided array into a single String
9873    * containing the provided list of elements.</p>
9874    *
9875    * <p>No separator is added to the joined String.
9876    * Null objects or empty strings within the array are represented by
9877    * empty strings.</p>
9878    *
9879    * <pre>
9880    * StringUtils.join(null)            = null
9881    * StringUtils.join([])              = ""
9882    * StringUtils.join([null])          = ""
9883    * StringUtils.join(["a", "b", "c"]) = "abc"
9884    * StringUtils.join([null, "", "a"]) = "a"
9885    * </pre>
9886    *
9887    * @param array  the array of values to join together, may be null
9888    * @return the joined String, <code>null</code> if null array input
9889    * @since 2.0
9890    */
9891   public static String join(Object[] array) {
9892     return join(array, null);
9893   }
9894 
9895   /**
9896    * <p>Joins the elements of the provided array into a single String
9897    * containing the provided list of elements.</p>
9898    *
9899    * <p>No delimiter is added before or after the list.
9900    * Null objects or empty strings within the array are represented by
9901    * empty strings.</p>
9902    *
9903    * <pre>
9904    * StringUtils.join(null, *)               = null
9905    * StringUtils.join([], *)                 = ""
9906    * StringUtils.join([null], *)             = ""
9907    * StringUtils.join(["a", "b", "c"], ';')  = "a;b;c"
9908    * StringUtils.join(["a", "b", "c"], null) = "abc"
9909    * StringUtils.join([null, "", "a"], ';')  = ";;a"
9910    * </pre>
9911    *
9912    * @param array  the array of values to join together, may be null
9913    * @param separator  the separator character to use
9914    * @return the joined String, <code>null</code> if null array input
9915    * @since 2.0
9916    */
9917   public static String join(Object[] array, char separator) {
9918     if (array == null) {
9919       return null;
9920     }
9921     int arraySize = array.length;
9922     int bufSize = (arraySize == 0 ? 0 : ((array[0] == null ? 16 : array[0].toString()
9923         .length()) + 1)
9924         * arraySize);
9925     StringBuffer buf = new StringBuffer(bufSize);
9926 
9927     for (int i = 0; i < arraySize; i++) {
9928       if (i > 0) {
9929         buf.append(separator);
9930       }
9931       if (array[i] != null) {
9932         buf.append(array[i]);
9933       }
9934     }
9935     return buf.toString();
9936   }
9937 
9938   /**
9939    * <p>Joins the elements of the provided array into a single String
9940    * containing the provided list of elements.</p>
9941    *
9942    * <p>No delimiter is added before or after the list.
9943    * A <code>null</code> separator is the same as an empty String ("").
9944    * Null objects or empty strings within the array are represented by
9945    * empty strings.</p>
9946    *
9947    * <pre>
9948    * StringUtils.join(null, *)                = null
9949    * StringUtils.join([], *)                  = ""
9950    * StringUtils.join([null], *)              = ""
9951    * StringUtils.join(["a", "b", "c"], "--")  = "a--b--c"
9952    * StringUtils.join(["a", "b", "c"], null)  = "abc"
9953    * StringUtils.join(["a", "b", "c"], "")    = "abc"
9954    * StringUtils.join([null, "", "a"], ',')   = ",,a"
9955    * </pre>
9956    *
9957    * @param array  the array of values to join together, may be null
9958    * @param separator  the separator character to use, null treated as ""
9959    * @return the joined String, <code>null</code> if null array input
9960    */
9961   public static String join(Object[] array, String separator) {
9962     if (array == null) {
9963       return null;
9964     }
9965     if (separator == null) {
9966       separator = "";
9967     }
9968     int arraySize = array.length;
9969 
9970     // ArraySize ==  0: Len = 0
9971     // ArraySize > 0:   Len = NofStrings *(len(firstString) + len(separator))
9972     //           (Assuming that all Strings are roughly equally long)
9973     int bufSize = ((arraySize == 0) ? 0 : arraySize
9974         * ((array[0] == null ? 16 : array[0].toString().length()) + separator.length()));
9975 
9976     StringBuffer buf = new StringBuffer(bufSize);
9977 
9978     for (int i = 0; i < arraySize; i++) {
9979       if (i > 0) {
9980         buf.append(separator);
9981       }
9982       if (array[i] != null) {
9983         buf.append(array[i]);
9984       }
9985     }
9986     return buf.toString();
9987   }
9988 
9989   /**
9990    * <p>Joins the elements of the provided <code>Iterator</code> into
9991    * a single String containing the provided elements.</p>
9992    *
9993    * <p>No delimiter is added before or after the list. Null objects or empty
9994    * strings within the iteration are represented by empty strings.</p>
9995    *
9996    * <p>See the examples here: {@link #join(Object[],char)}. </p>
9997    *
9998    * @param iterator  the <code>Iterator</code> of values to join together, may be null
9999    * @param separator  the separator character to use
10000    * @return the joined String, <code>null</code> if null iterator input
10001    * @since 2.0
10002    */
10003   public static String join(Iterator iterator, char separator) {
10004     if (iterator == null) {
10005       return null;
10006     }
10007     StringBuffer buf = new StringBuffer(256); // Java default is 16, probably too small
10008     while (iterator.hasNext()) {
10009       Object obj = iterator.next();
10010       if (obj != null) {
10011         buf.append(obj);
10012       }
10013       if (iterator.hasNext()) {
10014         buf.append(separator);
10015       }
10016     }
10017     return buf.toString();
10018   }
10019 
10020   /**
10021    * <p>Joins the elements of the provided <code>Iterator</code> into
10022    * a single String containing the provided elements.</p>
10023    *
10024    * <p>No delimiter is added before or after the list.
10025    * A <code>null</code> separator is the same as an empty String ("").</p>
10026    *
10027    * <p>See the examples here: {@link #join(Object[],String)}. </p>
10028    *
10029    * @param iterator  the <code>Iterator</code> of values to join together, may be null
10030    * @param separator  the separator character to use, null treated as ""
10031    * @return the joined String, <code>null</code> if null iterator input
10032    */
10033   public static String join(Iterator iterator, String separator) {
10034     if (iterator == null) {
10035       return null;
10036     }
10037     StringBuffer buf = new StringBuffer(256); // Java default is 16, probably too small
10038     while (iterator.hasNext()) {
10039       Object obj = iterator.next();
10040       if (obj != null) {
10041         buf.append(obj);
10042       }
10043       if ((separator != null) && iterator.hasNext()) {
10044         buf.append(separator);
10045       }
10046     }
10047     return buf.toString();
10048   }
10049 
10050   /**
10051    * <p>Returns either the passed in String,
10052    * or if the String is <code>null</code>, an empty String ("").</p>
10053    *
10054    * <pre>
10055    * StringUtils.defaultString(null)  = ""
10056    * StringUtils.defaultString("")    = ""
10057    * StringUtils.defaultString("bat") = "bat"
10058    * </pre>
10059    *
10060    * @see String#valueOf(Object)
10061    * @param str  the String to check, may be null
10062    * @return the passed in String, or the empty String if it
10063    *  was <code>null</code>
10064    */
10065   public static String defaultString(String str) {
10066     return str == null ? "" : str;
10067   }
10068 
10069   /**
10070    * <p>Returns either the passed in String, or if the String is
10071    * <code>null</code>, the value of <code>defaultStr</code>.</p>
10072    *
10073    * <pre>
10074    * StringUtils.defaultString(null, "NULL")  = "NULL"
10075    * StringUtils.defaultString("", "NULL")    = ""
10076    * StringUtils.defaultString("bat", "NULL") = "bat"
10077    * </pre>
10078    *
10079    * @see String#valueOf(Object)
10080    * @param str  the String to check, may be null
10081    * @param defaultStr  the default String to return
10082    *  if the input is <code>null</code>, may be null
10083    * @return the passed in String, or the default if it was <code>null</code>
10084    */
10085   public static String defaultString(String str, String defaultStr) {
10086     return str == null ? defaultStr : str;
10087   }
10088 
10089   /**
10090    * <p>Returns either the passed in String, or if the String is
10091    * empty or <code>null</code>, the value of <code>defaultStr</code>.</p>
10092    *
10093    * <pre>
10094    * StringUtils.defaultIfEmpty(null, "NULL")  = "NULL"
10095    * StringUtils.defaultIfEmpty("", "NULL")    = "NULL"
10096    * StringUtils.defaultIfEmpty("bat", "NULL") = "bat"
10097    * </pre>
10098    *
10099    * @param str  the String to check, may be null
10100    * @param defaultStr  the default String to return
10101    *  if the input is empty ("") or <code>null</code>, may be null
10102    * @return the passed in String, or the default
10103    */
10104   public static String defaultIfEmpty(String str, String defaultStr) {
10105     return isEmpty(str) ? defaultStr : str;
10106   }
10107 
10108   /**
10109    * <p>Capitalizes a String changing the first letter to title case as
10110    * per {@link Character#toTitleCase(char)}. No other letters are changed.</p>
10111    *
10112    * A <code>null</code> input String returns <code>null</code>.</p>
10113    *
10114    * <pre>
10115    * StringUtils.capitalize(null)  = null
10116    * StringUtils.capitalize("")    = ""
10117    * StringUtils.capitalize("cat") = "Cat"
10118    * StringUtils.capitalize("cAt") = "CAt"
10119    * </pre>
10120    *
10121    * @param str  the String to capitalize, may be null
10122    * @return the capitalized String, <code>null</code> if null String input
10123    * @since 2.0
10124    */
10125   public static String capitalize(String str) {
10126     int strLen;
10127     if (str == null || (strLen = str.length()) == 0) {
10128       return str;
10129     }
10130     return new StringBuffer(strLen).append(Character.toTitleCase(str.charAt(0))).append(
10131         str.substring(1)).toString();
10132   }
10133 
10134   /**
10135    * <p>Checks if String contains a search character, handling <code>null</code>.
10136    * This method uses {@link String#indexOf(int)}.</p>
10137    *
10138    * <p>A <code>null</code> or empty ("") String will return <code>false</code>.</p>
10139    *
10140    * <pre>
10141    * StringUtils.contains(null, *)    = false
10142    * StringUtils.contains("", *)      = false
10143    * StringUtils.contains("abc", 'a') = true
10144    * StringUtils.contains("abc", 'z') = false
10145    * </pre>
10146    *
10147    * @param str  the String to check, may be null
10148    * @param searchChar  the character to find
10149    * @return true if the String contains the search character,
10150    *  false if not or <code>null</code> string input
10151    * @since 2.0
10152    */
10153   public static boolean contains(String str, char searchChar) {
10154     if (isEmpty(str)) {
10155       return false;
10156     }
10157     return str.indexOf(searchChar) >= 0;
10158   }
10159 
10160   /**
10161    * <p>Checks if String contains a search String, handling <code>null</code>.
10162    * This method uses {@link String#indexOf(int)}.</p>
10163    *
10164    * <p>A <code>null</code> String will return <code>false</code>.</p>
10165    *
10166    * <pre>
10167    * StringUtils.contains(null, *)     = false
10168    * StringUtils.contains(*, null)     = false
10169    * StringUtils.contains("", "")      = true
10170    * StringUtils.contains("abc", "")   = true
10171    * StringUtils.contains("abc", "a")  = true
10172    * StringUtils.contains("abc", "z")  = false
10173    * </pre>
10174    *
10175    * @param str  the String to check, may be null
10176    * @param searchStr  the String to find, may be null
10177    * @return true if the String contains the search String,
10178    *  false if not or <code>null</code> string input
10179    * @since 2.0
10180    */
10181   public static boolean contains(String str, String searchStr) {
10182     if (str == null || searchStr == null) {
10183       return false;
10184     }
10185     return str.indexOf(searchStr) >= 0;
10186   }
10187 
10188   /**
10189    * An empty immutable <code>String</code> array.
10190    */
10191   public static final String[] EMPTY_STRING_ARRAY = new String[0];
10192 
10193   /**
10194    * <p>Compares two objects for equality, where either one or both
10195    * objects may be <code>null</code>.</p>
10196    *
10197    * <pre>
10198    * ObjectUtils.equals(null, null)                  = true
10199    * ObjectUtils.equals(null, "")                    = false
10200    * ObjectUtils.equals("", null)                    = false
10201    * ObjectUtils.equals("", "")                      = true
10202    * ObjectUtils.equals(Boolean.TRUE, null)          = false
10203    * ObjectUtils.equals(Boolean.TRUE, "true")        = false
10204    * ObjectUtils.equals(Boolean.TRUE, Boolean.TRUE)  = true
10205    * ObjectUtils.equals(Boolean.TRUE, Boolean.FALSE) = false
10206    * </pre>
10207    *
10208    * @param object1  the first object, may be <code>null</code>
10209    * @param object2  the second object, may be <code>null</code>
10210    * @return <code>true</code> if the values of both objects are the same
10211    */
10212   public static boolean equals(Object object1, Object object2) {
10213       if (object1 == object2) {
10214           return true;
10215       }
10216       if ((object1 == null) || (object2 == null)) {
10217           return false;
10218       }
10219       if (object1 instanceof Date && object2 instanceof Date) {
10220         return ((Date)object1).getTime() == ((Date)object2).getTime();
10221       }
10222       return object1.equals(object2);
10223   }
10224 
10225   /**
10226    * see if two sets are deep equals using the equals() method on each item
10227    * @param set1
10228    * @param set2
10229    * @return if lists are equal or empty
10230    */
10231   public static boolean equalsSet(Set<?> set1, Set<?> set2) {
10232     if (length(set1) == length(set2) && length(set1) == 0) {
10233       return true;
10234     }
10235     if (set1 == null || set2 == null) {
10236       return false;
10237     }
10238     
10239     if (set1.size() != set2.size()) {
10240       return false;
10241     }
10242     Set<?> newSet = new HashSet(set1);
10243     newSet.removeAll(set2);
10244     return newSet.size() == 0;
10245   }
10246 
10247   /**
10248    * see if two lists are deep equals using the equals() method on each item
10249    * @param list1
10250    * @param list2
10251    * @return if lists are equal
10252    */
10253   public static boolean equalsList(List<?> list1, List<?> list2) {
10254     if (list1 == list2) {
10255       return true;
10256     }
10257     if (list1 == null || list2 == null) {
10258       return false;
10259     }
10260     if (list1.size() != list2.size()) {
10261       return false;
10262     }
10263     for (int i = 0; i < list1.size(); ++i) {
10264       if (!equals(list1.get(i), list2.get(i))) {
10265         return false;
10266       }
10267     }
10268 
10269     return true;
10270   }
10271   
10272   /**
10273    * <p>A way to get the entire nested stack-trace of an throwable.</p>
10274    *
10275    * @param throwable  the <code>Throwable</code> to be examined
10276    * @return the nested stack trace, with the root cause first
10277    * @since 2.0
10278    */
10279   public static String getFullStackTrace(Throwable throwable) {
10280       StringWriter sw = new StringWriter();
10281       PrintWriter pw = new PrintWriter(sw, true);
10282       Throwable[] ts = getThrowables(throwable);
10283       for (int i = 0; i < ts.length; i++) {
10284           ts[i].printStackTrace(pw);
10285           if (isNestedThrowable(ts[i])) {
10286               break;
10287           }
10288       }
10289       return sw.getBuffer().toString();
10290   }
10291 
10292   /** true or false for if we know if this is a class or not */
10293   private static Map<String, Boolean> jexlKnowsIfClass = new HashMap<String, Boolean>();
10294 
10295   /** class object for this string */
10296   private static Map<String, Class<?>> jexlClass = new HashMap<String, Class<?>>();
10297 
10298   /** pattern to see if class or not */
10299   private static Pattern jexlClassPattern = Pattern.compile("^[a-zA-Z0-9_.]*\\.[A-Z][a-zA-Z0-9_]*$");
10300 
10301   /**
10302    *
10303    */
10304   private static class GrouperMapContext extends MapContext {
10305 
10306     /**
10307      * retrieve class if class
10308      * @param name
10309      * @return class
10310      */
10311     private static Object retrieveClass(String name) {
10312       if (isBlank(name)) {
10313         return null;
10314       }
10315 
10316       //see if fully qualified class
10317 
10318       Boolean knowsIfClass = jexlKnowsIfClass.get(name);
10319 
10320       //see if knows answer
10321       if (knowsIfClass != null) {
10322         //return class or null
10323         return jexlClass.get(name);
10324       }
10325 
10326       //see if valid class
10327       if (jexlClassPattern.matcher(name).matches()) {
10328 
10329         jexlKnowsIfClass.put(name, true);
10330         //try to load
10331         try {
10332           Class<?> theClass = Class.forName(name);
10333           jexlClass.put(name, theClass);
10334           return theClass;
10335         } catch (Exception e) {
10336           LOG.info("Cant load what looks like class: " + name, e);
10337           //this is ok I guess, dont rethrow, not sure it is a class
10338         }
10339       }
10340       return null;
10341 
10342     }
10343 
10344     /**
10345      * @see org.apache.commons.jexl2.MapContext#get(java.lang.String)
10346      */
10347     @Override
10348     public Object get(String name) {
10349 
10350       //see if registered
10351       Object object = super.get(name);
10352 
10353       if (object != null) {
10354         return object;
10355       }
10356       return retrieveClass(name);
10357     }
10358 
10359     /**
10360      * @see org.apache.commons.jexl2.MapContext#has(java.lang.String)
10361      */
10362     @Override
10363     public boolean has(String name) {
10364       boolean superHas = super.has(name);
10365       if (superHas) {
10366         return true;
10367       }
10368 
10369       return retrieveClass(name) != null;
10370 
10371     }
10372 
10373   }
10374 
10375   /**
10376   *
10377   */
10378  private static class GrouperMapContext3 extends org.apache.commons.jexl3.MapContext {
10379 
10380    /**
10381     * retrieve class if class
10382     * @param name
10383     * @return class
10384     */
10385    private static Object retrieveClass(String name) {
10386      if (isBlank(name)) {
10387        return null;
10388      }
10389 
10390      //see if fully qualified class
10391 
10392      Boolean knowsIfClass = jexlKnowsIfClass.get(name);
10393 
10394      //see if knows answer
10395      if (knowsIfClass != null) {
10396        //return class or null
10397        return jexlClass.get(name);
10398      }
10399 
10400      //see if valid class
10401      if (jexlClassPattern.matcher(name).matches()) {
10402 
10403        jexlKnowsIfClass.put(name, true);
10404        //try to load
10405        try {
10406          Class<?> theClass = Class.forName(name);
10407          jexlClass.put(name, theClass);
10408          return theClass;
10409        } catch (Exception e) {
10410          LOG.info("Cant load what looks like class: " + name, e);
10411          //this is ok I guess, dont rethrow, not sure it is a class
10412        }
10413      }
10414      return null;
10415 
10416    }
10417 
10418    /**
10419     * @see org.apache.commons.jexl2.MapContext#get(java.lang.String)
10420     */
10421    @Override
10422    public Object get(String name) {
10423 
10424      //see if registered
10425      Object object = super.get(name);
10426 
10427      if (object != null) {
10428        return object;
10429      }
10430      return retrieveClass(name);
10431    }
10432 
10433    /**
10434     * @see org.apache.commons.jexl2.MapContext#has(java.lang.String)
10435     */
10436    @Override
10437    public boolean has(String name) {
10438      boolean superHas = super.has(name);
10439      if (superHas) {
10440        return true;
10441      }
10442 
10443      return retrieveClass(name) != null;
10444 
10445    }
10446 
10447 
10448 
10449 
10450  }
10451 
10452   /**
10453    * substitute an EL for objects
10454    * @param stringToParse
10455    * @param variableMap
10456    * @return the string
10457    */
10458   @SuppressWarnings("unchecked")
10459   public static String substituteExpressionLanguage(String stringToParse, Map<String, Object> variableMap) {
10460     //by default dont allow static classes
10461     return substituteExpressionLanguage(stringToParse, variableMap, false, false);
10462 
10463   }
10464 
10465   /**
10466    * substitute an EL for objects
10467    * @param stringToParse
10468    * @param variableMap
10469    * @param allowStaticClasses if true allow static classes not registered with context
10470    * @return the string
10471    */
10472   @SuppressWarnings("unchecked")
10473   @Deprecated
10474   public static String substituteExpressionLanguage(String stringToParse,
10475       Map<String, Object> variableMap, boolean allowStaticClasses) {
10476     return substituteExpressionLanguage(stringToParse, variableMap, allowStaticClasses, false);
10477   }
10478 
10479   /**
10480    * substitute an EL for objects
10481    * @param stringToParse
10482    * @param variableMap
10483    * @param allowStaticClasses if true allow static classes not registered with context
10484    * @param silent if silent mode, swallow exceptions (warn), and dont warn when variable not found
10485    * @return the string
10486    */
10487   @SuppressWarnings("unchecked")
10488   public static String substituteExpressionLanguage(String stringToParse,
10489       Map<String, Object> variableMap, boolean allowStaticClasses, boolean silent) {
10490     return substituteExpressionLanguage(stringToParse, variableMap, allowStaticClasses, silent, false);
10491   }
10492 
10493 //  /**
10494 //   * text container pattern
10495 //   */
10496 //  private static Pattern textContainerPattern = Pattern.compile("^\\s*textContainer.(text[a-zA-Z]*)\\[\\s*['\"]([a-zA-Z0-9_]+)['\"]\\s*\\]\\s*$");
10497   
10498   /**
10499    * pattern to identify a script
10500    */
10501   private static Pattern scriptPattern = Pattern.compile("\\$\\{(.*?)\\}");
10502   
10503   /**
10504    * substitute an EL for objects
10505    * @param stringToParse
10506    * @param variableMap
10507    * @param allowStaticClasses if true allow static classes not registered with context
10508    * @param silent if silent mode, swallow exceptions (warn), and dont warn when variable not found
10509    * @param lenient false if undefined variables should throw an exception.  if lenient is true (default)
10510    * then undefined variables are null
10511    * @return the string
10512    */
10513   @SuppressWarnings("unchecked")
10514   public static String substituteExpressionLanguage(String stringToParse,
10515       Map<String, Object> variableMap, boolean allowStaticClasses, boolean silent, boolean lenient) {
10516     variableMap = nonNull(variableMap);
10517     substituteExpressionInit();
10518     
10519     if (isBlank(stringToParse)) {
10520       return stringToParse;
10521     }
10522     String overallResult = null;
10523     Exception exception = null;
10524     try {
10525       JexlContext jc = allowStaticClasses ? new GrouperMapContext() : new MapContext();
10526 
10527       int index = 0;
10528 
10529       for (String key: variableMap.keySet()) {
10530         jc.set(key, variableMap.get(key));
10531       }
10532 
10533       //allow utility methods
10534       jc.set("grouperUtil", new GrouperUtilElSafe());
10535       //if you add another one here, add it in the logs below
10536 
10537       // matching ${ exp }   (non-greedy)
10538       Matcher matcher = scriptPattern.matcher(stringToParse);
10539 
10540       StringBuilder result = new StringBuilder();
10541 
10542       //loop through and find each script
10543       while(matcher.find()) {
10544         result.append(stringToParse.substring(index, matcher.start()));
10545 
10546         //here is the script inside the curlies
10547         String script = matcher.group(1);
10548 
10549         index = matcher.end();
10550 
10551         if (script.contains("{")) {
10552           //we need to match up some curlies here...
10553           int scriptStart = matcher.start(1);
10554           int openCurlyCount = 0;
10555           for (int i=scriptStart; i<stringToParse.length();i++) {
10556             char curChar = stringToParse.charAt(i);
10557             if (curChar == '{') {
10558               openCurlyCount++;
10559             }
10560             if (curChar == '}') {
10561               openCurlyCount--;
10562               //negative 1 since we need to get to the close of the parent one...
10563               if (openCurlyCount <= -1) {
10564                 script = stringToParse.substring(scriptStart, i);
10565                 index = i+1;
10566                 break;
10567               }
10568             }
10569           }
10570         }
10571 
10572         //this is the result of the evaluation
10573         Object o = null;
10574 
10575 //        // if this is a text container, then scripts in the externalized text might not work, so substitute that out here
10576 //        Object textContainer = variableMap.get("textContainer");
10577 //        matcher = textContainerPattern.matcher(script);
10578 //        if (textContainer != null && matcher.matches()) {
10579 //
10580 //          String mapName = matcher.group(1);
10581 //          String key = matcher.group(2);
10582 //          
10583 //          String methodName = "get" + Character.toUpperCase(mapName.charAt(0)) + mapName.substring(1, mapName.length());
10584 //
10585 //          Map<String, String> theMap = (Map<String, String>)callMethod(textContainer, methodName);
10586 //          
10587 //          o = theMap.get(key);
10588 //          
10589 //        } else {
10590           
10591         Expression e = jexlEngines.get(new MultiKey(silent, lenient)).createExpression(script);
10592 
10593         try {
10594           o = e.evaluate(jc);
10595         } catch (JexlException je) {
10596           //exception-scrape to see if missing variable
10597           if (!lenient && StringUtils.trimToEmpty(je.getMessage()).contains("undefined variable")) {
10598             //clean up the message a little bit
10599             // e.g. edu.internet2.middleware.grouper.util.GrouperUtil.substituteExpressionLanguage@8846![0,6]: 'amount < 50000 && amount2 < 23;' undefined variable amount
10600             String message = je.getMessage();
10601             //Pattern exceptionPattern = Pattern.compile("^" + GrouperUtil.class.getName() + "\\.substituteExpressionLanguage.*?]: '(.*)");
10602             Pattern exceptionPattern = Pattern.compile("^.*undefined variable (.*)");
10603             Matcher exceptionMatcher = exceptionPattern.matcher(message);
10604             if (exceptionMatcher.matches()) {
10605               //message = "'" + exceptionMatcher.group(1);
10606               message = "variable '" + exceptionMatcher.group(1) + "' is not defined in script: '" + script + "'";
10607             }
10608             throw new ExpressionLanguageMissingVariableException(message, je);
10609           }
10610           throw je;
10611         }
10612 
10613 //        }
10614         //we dont want "null" in the result I think...
10615         if (o == null && lenient) {
10616           o = "";
10617         }
10618         
10619         if (o == null) {
10620           LOG.warn("expression returned null: " + script + ", in pattern: '" + stringToParse + "', available variables are: "
10621               + toStringForLog(variableMap.keySet()));
10622         }
10623 
10624         if (o instanceof RuntimeException) {
10625           throw (RuntimeException)o;
10626         }
10627 
10628         result.append(o);
10629 
10630       }
10631 
10632       result.append(stringToParse.substring(index, stringToParse.length()));
10633       overallResult = result.toString();
10634       return overallResult;
10635 
10636     } catch (HookVeto hv) {
10637       exception = hv;
10638       throw hv;
10639     } catch (Exception e) {
10640       exception = e;
10641       if (e instanceof ExpressionLanguageMissingVariableException) {
10642         throw (ExpressionLanguageMissingVariableException)e;
10643       }
10644       throw new RuntimeException("Error substituting string: '" + stringToParse + "'", e);
10645     } finally {
10646       if (LOG.isDebugEnabled()) {
10647         Set<String> keysSet = new LinkedHashSet<String>(nonNull(variableMap).keySet());
10648         keysSet.add("grouperUtil");
10649         StringBuilder logMessage = new StringBuilder();
10650         logMessage.append("Subsituting EL: '").append(stringToParse).append("', and with env vars: ");
10651         String[] keys = keysSet.toArray(new String[]{});
10652         for (int i=0;i<keys.length;i++) {
10653           logMessage.append(keys[i]);
10654           if (i != keys.length-1) {
10655             logMessage.append(", ");
10656           }
10657         }
10658         logMessage.append(" with result: '" + overallResult + "'");
10659         if (exception != null) {
10660           if (exception instanceof HookVeto) {
10661             logMessage.append(", it was vetoed: " + exception);
10662           } else {
10663             logMessage.append(", and exception: " + exception + ", " + ExceptionUtils.getFullStackTrace(exception));
10664           }
10665         }
10666         LOG.debug(logMessage.toString());
10667       }
10668     }
10669   }
10670 
10671   /**
10672    * substitute an EL for objects
10673    * @param stringToParse
10674    * @param variableMap
10675    * @param allowStaticClasses if true allow static classes not registered with context
10676    * @param silent if silent mode, swallow exceptions (warn), and dont warn when variable not found
10677    * @param lenient false if undefined variables should throw an exception.  if lenient is true (default)
10678    * then undefined variables are null
10679    * @return the object
10680    */
10681   @SuppressWarnings("unchecked")
10682   public static Object substituteExpressionLanguageScript(String script,
10683       Map<String, Object> variableMap, boolean allowStaticClasses, boolean silent, boolean lenient) {
10684     variableMap = nonNull(variableMap);
10685     substituteExpressionInit();
10686     
10687     if (isBlank(script)) {
10688       return null;
10689     }
10690     String overallResult = null;
10691     Exception exception = null;
10692     try {
10693       JexlContext jc = allowStaticClasses ? new GrouperMapContext() : new MapContext();
10694 
10695       int index = 0;
10696 
10697       for (String key: variableMap.keySet()) {
10698         jc.set(key, variableMap.get(key));
10699       }
10700 
10701       //allow utility methods
10702       jc.set("grouperUtil", new GrouperUtilElSafe());
10703       //if you add another one here, add it in the logs below
10704 
10705       script = script.trim();
10706       if (!script.startsWith("${") || !script.endsWith("}")) {
10707         throw new RuntimeException("Script must be ${script}: '" + script + "'");
10708       }
10709       // take out ${  and  }
10710       script = script.substring(2, script.length()-1);
10711 
10712       Script e = jexlEngines.get(new MultiKey(silent, lenient)).createScript(script);
10713 
10714       //this is the result of the evaluation
10715       Object o = null;
10716 
10717       try {
10718         o = e.execute(jc);
10719       } catch (JexlException je) {
10720         //exception-scrape to see if missing variable
10721         if (!lenient && StringUtils.trimToEmpty(je.getMessage()).contains("undefined variable")) {
10722           //clean up the message a little bit
10723           // e.g. edu.internet2.middleware.grouper.util.GrouperUtil.substituteExpressionLanguage@8846![0,6]: 'amount < 50000 && amount2 < 23;' undefined variable amount
10724           String message = je.getMessage();
10725           //Pattern exceptionPattern = Pattern.compile("^" + GrouperUtil.class.getName() + "\\.substituteExpressionLanguage.*?]: '(.*)");
10726           Pattern exceptionPattern = Pattern.compile("^.*undefined variable (.*)");
10727           Matcher exceptionMatcher = exceptionPattern.matcher(message);
10728           if (exceptionMatcher.matches()) {
10729             //message = "'" + exceptionMatcher.group(1);
10730             message = "variable '" + exceptionMatcher.group(1) + "' is not defined in script: '" + script + "'";
10731           }
10732           throw new ExpressionLanguageMissingVariableException(message, je);
10733         }
10734         throw je;
10735       }
10736 
10737       if (o instanceof RuntimeException) {
10738         throw (RuntimeException)o;
10739       }
10740 
10741       return o;
10742     } catch (HookVeto hv) {
10743       exception = hv;
10744       throw hv;
10745     } catch (Exception e) {
10746       exception = e;
10747       if (e instanceof ExpressionLanguageMissingVariableException) {
10748         throw (ExpressionLanguageMissingVariableException)e;
10749       }
10750       throw new RuntimeException("Error substituting string: '" + script + "'", e);
10751     } finally {
10752       if (LOG.isDebugEnabled()) {
10753         Set<String> keysSet = new LinkedHashSet<String>(nonNull(variableMap).keySet());
10754         keysSet.add("grouperUtil");
10755         StringBuilder logMessage = new StringBuilder();
10756         logMessage.append("Subsituting EL: '").append(script).append("', and with env vars: ");
10757         String[] keys = keysSet.toArray(new String[]{});
10758         for (int i=0;i<keys.length;i++) {
10759           logMessage.append(keys[i]);
10760           if (i != keys.length-1) {
10761             logMessage.append(", ");
10762           }
10763         }
10764         logMessage.append(" with result: '" + overallResult + "'");
10765         if (exception != null) {
10766           if (exception instanceof HookVeto) {
10767             logMessage.append(", it was vetoed: " + exception);
10768           } else {
10769             logMessage.append(", and exception: " + exception + ", " + ExceptionUtils.getFullStackTrace(exception));
10770           }
10771         }
10772         LOG.debug(logMessage.toString());
10773       }
10774     }
10775   }
10776 
10777   /**
10778    * <p>Returns the list of <code>Throwable</code> objects in the
10779    * exception chain.</p>
10780    *
10781    * <p>A throwable without cause will return an array containing
10782    * one element - the input throwable.
10783    * A throwable with one cause will return an array containing
10784    * two elements. - the input throwable and the cause throwable.
10785    * A <code>null</code> throwable will return an array size zero.</p>
10786    *
10787    * @param throwable  the throwable to inspect, may be null
10788    * @return the array of throwables, never null
10789    */
10790   public static Throwable[] getThrowables(Throwable throwable) {
10791       List list = new ArrayList();
10792       while (throwable != null) {
10793           list.add(throwable);
10794           throwable = getCause(throwable);
10795       }
10796       return (Throwable[]) list.toArray(new Throwable[list.size()]);
10797   }
10798 
10799   /**
10800    * <p>The names of methods commonly used to access a wrapped exception.</p>
10801    */
10802   private static String[] CAUSE_METHOD_NAMES = {
10803       "getCause",
10804       "getNextException",
10805       "getTargetException",
10806       "getException",
10807       "getSourceException",
10808       "getRootCause",
10809       "getCausedByException",
10810       "getNested",
10811       "getLinkedException",
10812       "getNestedException",
10813       "getLinkedCause",
10814       "getThrowable",
10815   };
10816 
10817   /**
10818    * <p>Checks whether this <code>Throwable</code> class can store a cause.</p>
10819    *
10820    * <p>This method does <b>not</b> check whether it actually does store a cause.<p>
10821    *
10822    * @param throwable  the <code>Throwable</code> to examine, may be null
10823    * @return boolean <code>true</code> if nested otherwise <code>false</code>
10824    * @since 2.0
10825    */
10826   public static boolean isNestedThrowable(Throwable throwable) {
10827       if (throwable == null) {
10828           return false;
10829       }
10830 
10831       if (throwable instanceof Nestable) {
10832           return true;
10833       } else if (throwable instanceof SQLException) {
10834           return true;
10835       } else if (throwable instanceof InvocationTargetException) {
10836           return true;
10837       } else if (isThrowableNested()) {
10838           return true;
10839       }
10840 
10841       Class cls = throwable.getClass();
10842       for (int i = 0, isize = CAUSE_METHOD_NAMES.length; i < isize; i++) {
10843           try {
10844               Method method = cls.getMethod(CAUSE_METHOD_NAMES[i], (Class[])null);
10845               if (method != null && Throwable.class.isAssignableFrom(method.getReturnType())) {
10846                   return true;
10847               }
10848           } catch (NoSuchMethodException ignored) {
10849           } catch (SecurityException ignored) {
10850           }
10851       }
10852 
10853       try {
10854           Field field = cls.getField("detail");
10855           if (field != null) {
10856               return true;
10857           }
10858       } catch (NoSuchFieldException ignored) {
10859       } catch (SecurityException ignored) {
10860       }
10861 
10862       return false;
10863   }
10864 
10865   /**
10866    * <p>The Method object for JDK1.4 getCause.</p>
10867    */
10868   private static final Method THROWABLE_CAUSE_METHOD;
10869   static {
10870       Method getCauseMethod;
10871       try {
10872           getCauseMethod = Throwable.class.getMethod("getCause", (Class[])null);
10873       } catch (Exception e) {
10874           getCauseMethod = null;
10875       }
10876       THROWABLE_CAUSE_METHOD = getCauseMethod;
10877   }
10878 
10879   /**
10880    * <p>Checks if the Throwable class has a <code>getCause</code> method.</p>
10881    *
10882    * <p>This is true for JDK 1.4 and above.</p>
10883    *
10884    * @return true if Throwable is nestable
10885    * @since 2.0
10886    */
10887   public static boolean isThrowableNested() {
10888       return THROWABLE_CAUSE_METHOD != null;
10889   }
10890 
10891   /**
10892    * <p>Introspects the <code>Throwable</code> to obtain the cause.</p>
10893    *
10894    * <p>The method searches for methods with specific names that return a
10895    * <code>Throwable</code> object. This will pick up most wrapping exceptions,
10896    * including those from JDK 1.4, and
10897    * {@link org.apache.commons.lang.exception.NestableException NestableException}.</p>
10898    *
10899    * <p>The default list searched for are:</p>
10900    * <ul>
10901    *  <li><code>getCause()</code></li>
10902    *  <li><code>getNextException()</code></li>
10903    *  <li><code>getTargetException()</code></li>
10904    *  <li><code>getException()</code></li>
10905    *  <li><code>getSourceException()</code></li>
10906    *  <li><code>getRootCause()</code></li>
10907    *  <li><code>getCausedByException()</code></li>
10908    *  <li><code>getNested()</code></li>
10909    * </ul>
10910    *
10911    * <p>In the absence of any such method, the object is inspected for a
10912    * <code>detail</code> field assignable to a <code>Throwable</code>.</p>
10913    *
10914    * <p>If none of the above is found, returns <code>null</code>.</p>
10915    *
10916    * @param throwable  the throwable to introspect for a cause, may be null
10917    * @return the cause of the <code>Throwable</code>,
10918    *  <code>null</code> if none found or null throwable input
10919    * @since 1.0
10920    */
10921   public static Throwable getCause(Throwable throwable) {
10922       return getCause(throwable, CAUSE_METHOD_NAMES);
10923   }
10924 
10925   /**
10926    * <p>Introspects the <code>Throwable</code> to obtain the cause.</p>
10927    *
10928    * <ol>
10929    * <li>Try known exception types.</li>
10930    * <li>Try the supplied array of method names.</li>
10931    * <li>Try the field 'detail'.</li>
10932    * </ol>
10933    *
10934    * <p>A <code>null</code> set of method names means use the default set.
10935    * A <code>null</code> in the set of method names will be ignored.</p>
10936    *
10937    * @param throwable  the throwable to introspect for a cause, may be null
10938    * @param methodNames  the method names, null treated as default set
10939    * @return the cause of the <code>Throwable</code>,
10940    *  <code>null</code> if none found or null throwable input
10941    * @since 1.0
10942    */
10943   public static Throwable getCause(Throwable throwable, String[] methodNames) {
10944       if (throwable == null) {
10945           return null;
10946       }
10947       Throwable cause = getCauseUsingWellKnownTypes(throwable);
10948       if (cause == null) {
10949           if (methodNames == null) {
10950               methodNames = CAUSE_METHOD_NAMES;
10951           }
10952           for (int i = 0; i < methodNames.length; i++) {
10953               String methodName = methodNames[i];
10954               if (methodName != null) {
10955                   cause = getCauseUsingMethodName(throwable, methodName);
10956                   if (cause != null) {
10957                       break;
10958                   }
10959               }
10960           }
10961 
10962           if (cause == null) {
10963               cause = getCauseUsingFieldName(throwable, "detail");
10964           }
10965       }
10966       return cause;
10967   }
10968 
10969   /**
10970    * <p>Finds a <code>Throwable</code> by method name.</p>
10971    *
10972    * @param throwable  the exception to examine
10973    * @param methodName  the name of the method to find and invoke
10974    * @return the wrapped exception, or <code>null</code> if not found
10975    */
10976   private static Throwable getCauseUsingMethodName(Throwable throwable, String methodName) {
10977       Method method = null;
10978       try {
10979           method = throwable.getClass().getMethod(methodName, (Class[])null);
10980       } catch (NoSuchMethodException ignored) {
10981       } catch (SecurityException ignored) {
10982       }
10983 
10984       if (method != null && Throwable.class.isAssignableFrom(method.getReturnType())) {
10985           try {
10986               return (Throwable) method.invoke(throwable, EMPTY_OBJECT_ARRAY);
10987           } catch (IllegalAccessException ignored) {
10988           } catch (IllegalArgumentException ignored) {
10989           } catch (InvocationTargetException ignored) {
10990           }
10991       }
10992       return null;
10993   }
10994 
10995   /**
10996    * <p>Finds a <code>Throwable</code> by field name.</p>
10997    *
10998    * @param throwable  the exception to examine
10999    * @param fieldName  the name of the attribute to examine
11000    * @return the wrapped exception, or <code>null</code> if not found
11001    */
11002   private static Throwable getCauseUsingFieldName(Throwable throwable, String fieldName) {
11003       Field field = null;
11004       try {
11005           field = throwable.getClass().getField(fieldName);
11006       } catch (NoSuchFieldException ignored) {
11007       } catch (SecurityException ignored) {
11008       }
11009 
11010       if (field != null && Throwable.class.isAssignableFrom(field.getType())) {
11011           try {
11012               return (Throwable) field.get(throwable);
11013           } catch (IllegalAccessException ignored) {
11014           } catch (IllegalArgumentException ignored) {
11015           }
11016       }
11017       return null;
11018   }
11019 
11020   /**
11021    * <p>Finds a <code>Throwable</code> for known types.</p>
11022    *
11023    * <p>Uses <code>instanceof</code> checks to examine the exception,
11024    * looking for well known types which could contain chained or
11025    * wrapped exceptions.</p>
11026    *
11027    * @param throwable  the exception to examine
11028    * @return the wrapped exception, or <code>null</code> if not found
11029    */
11030   private static Throwable getCauseUsingWellKnownTypes(Throwable throwable) {
11031       if (throwable instanceof Nestable) {
11032           return ((Nestable) throwable).getCause();
11033       } else if (throwable instanceof SQLException) {
11034           return ((SQLException) throwable).getNextException();
11035       } else if (throwable instanceof InvocationTargetException) {
11036           return ((InvocationTargetException) throwable).getTargetException();
11037       } else {
11038           return null;
11039       }
11040   }
11041 
11042   /**
11043    * An empty immutable <code>Object</code> array.
11044    */
11045   public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
11046 
11047   /**
11048    * Copy bytes from an <code>InputStream</code> to chars on a
11049    * <code>Writer</code> using the default character encoding of the platform.
11050    * <p>
11051    * This method buffers the input internally, so there is no need to use a
11052    * <code>BufferedInputStream</code>.
11053    * <p>
11054    * This method uses {@link InputStreamReader}.
11055    *
11056    * @param input  the <code>InputStream</code> to read from
11057    * @param output  the <code>Writer</code> to write to
11058    * @throws NullPointerException if the input or output is null
11059    * @throws IOException if an I/O error occurs
11060    * @since Commons IO 1.1
11061    */
11062   public static void copy(InputStream input, Writer output)
11063           throws IOException {
11064       InputStreamReader in = new InputStreamReader(input);
11065       copy(in, output);
11066   }
11067 
11068   /**
11069    * get a jar file from a sample class
11070    * @param sampleClass
11071    * @param printError if error should be printed when there is a problem
11072    * @return the jar file
11073    */
11074   public static File jarFile(Class sampleClass, boolean printError) {
11075     try {
11076       CodeSource codeSource = sampleClass.getProtectionDomain().getCodeSource();
11077       if (codeSource != null && codeSource.getLocation() != null) {
11078         return new File(codeSource.getLocation().getFile());
11079       }
11080       String resourcePath = sampleClass.getName();
11081       resourcePath = resourcePath.replace('.', '/') + ".class";
11082       URL url = computeUrl(resourcePath, true);
11083       String urlPath = url.toString();
11084 
11085       if (urlPath.startsWith("jar:")) {
11086         urlPath = urlPath.substring(4);
11087       }
11088       if (urlPath.startsWith("file:")) {
11089         urlPath = urlPath.substring(5);
11090       }
11091       urlPath = prefixOrSuffix(urlPath, "!", true);
11092 
11093       File file = new File(urlPath);
11094       if (urlPath.endsWith(".jar") && file.exists() && file.isFile()) {
11095         return file;
11096       }
11097     } catch (Exception e) {
11098       if (printError) {
11099         e.printStackTrace();
11100         System.err.println("Cant find jar for class: " + sampleClass + ", " + e.getMessage());
11101       }
11102     }
11103     return null;
11104   }
11105 
11106   /**
11107    * strip the last slash (/ or \) from a string if it exists
11108    *
11109    * @param input
11110    *
11111    * @return input - the last / or \
11112    */
11113   public static String stripLastSlashIfExists(String input) {
11114     if ((input == null) || (input.length() == 0)) {
11115       return input;
11116     }
11117 
11118     char lastChar = input.charAt(input.length() - 1);
11119 
11120     if ((lastChar == '\\') || (lastChar == '/')) {
11121       return input.substring(0, input.length() - 1);
11122     }
11123 
11124     return input;
11125   }
11126 
11127   /**
11128    * strip the first slash (/ or \) from a string if it exists
11129    *
11130    * @param input
11131    *
11132    * @return input - the last / or \
11133    */
11134   public static String stripFirstSlashIfExists(String input) {
11135     if ((input == null) || (input.length() == 0)) {
11136       return input;
11137     }
11138 
11139     char firstChar = input.charAt(0);
11140 
11141     if ((firstChar == '\\') || (firstChar == '/')) {
11142       return input.substring(1, input.length());
11143     }
11144 
11145     return input;
11146   }
11147 
11148   /**
11149    * retrieve a password from stdin
11150    * @param dontMask
11151    * @param prompt to print to user
11152    * @return the password
11153    */
11154   public static String retrievePasswordFromStdin(boolean dontMask, String prompt) {
11155     String passwordString = null;
11156 
11157     if (dontMask) {
11158 
11159       System.out.print(prompt);
11160       //  open up standard input
11161       BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
11162 
11163       //  read the username from the command-line; need to use try/catch with the
11164       //  readLine() method
11165       try {
11166          passwordString = br.readLine();
11167       } catch (IOException ioe) {
11168          System.out.println("IO error! " + getFullStackTrace(ioe));
11169          System.exit(1);
11170       }
11171 
11172     } else {
11173       char password[] = null;
11174       try {
11175         password = retrievePasswordFromStdin(System.in, prompt);
11176       } catch (IOException ioe) {
11177         ioe.printStackTrace();
11178       }
11179       passwordString = String.valueOf(password);
11180     }
11181     return passwordString;
11182 
11183   }
11184 
11185   /**
11186    * @param in stream to be used (e.g. System.in)
11187    * @param prompt The prompt to display to the user.
11188    * @return The password as entered by the user.
11189    * @throws IOException
11190    */
11191   public static final char[] retrievePasswordFromStdin(InputStream in, String prompt) throws IOException {
11192     MaskingThread maskingthread = new MaskingThread(prompt);
11193 
11194     Thread thread = new Thread(maskingthread);
11195     thread.start();
11196 
11197     char[] lineBuffer;
11198     char[] buf;
11199 
11200     buf = lineBuffer = new char[128];
11201 
11202     int room = buf.length;
11203     int offset = 0;
11204     int c;
11205 
11206     loop: while (true) {
11207       switch (c = in.read()) {
11208         case -1:
11209         case '\n':
11210           break loop;
11211 
11212         case '\r':
11213           int c2 = in.read();
11214           if ((c2 != '\n') && (c2 != -1)) {
11215             if (!(in instanceof PushbackInputStream)) {
11216               in = new PushbackInputStream(in);
11217             }
11218             ((PushbackInputStream) in).unread(c2);
11219           } else {
11220             break loop;
11221           }
11222 
11223         default:
11224           if (--room < 0) {
11225             buf = new char[offset + 128];
11226             room = buf.length - offset - 1;
11227             System.arraycopy(lineBuffer, 0, buf, 0, offset);
11228             Arrays.fill(lineBuffer, ' ');
11229             lineBuffer = buf;
11230           }
11231           buf[offset++] = (char) c;
11232           break;
11233       }
11234     }
11235     maskingthread.stopMasking();
11236     if (offset == 0) {
11237       return null;
11238     }
11239     char[] ret = new char[offset];
11240     System.arraycopy(buf, 0, ret, 0, offset);
11241     Arrays.fill(buf, ' ');
11242     return ret;
11243   }
11244 
11245   /**
11246    * thread to mask input
11247    */
11248   static class MaskingThread extends Thread {
11249 
11250     /** stop */
11251     private volatile boolean stop;
11252 
11253     /** echo char, this doesnt work correctly, so make a space so people dont notice...
11254      * prints out too many */
11255     private char echochar = ' ';
11256 
11257     /**
11258      *@param prompt The prompt displayed to the user
11259      */
11260     public MaskingThread(String prompt) {
11261       System.out.print(prompt);
11262     }
11263 
11264     /**
11265      * Begin masking until asked to stop.
11266      */
11267     @Override
11268     public void run() {
11269 
11270       int priority = Thread.currentThread().getPriority();
11271       Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
11272 
11273       try {
11274         this.stop = true;
11275         while (this.stop) {
11276           System.out.print("\010" + this.echochar);
11277           try {
11278             // attempt masking at this rate
11279             Thread.sleep(1);
11280           } catch (InterruptedException iex) {
11281             Thread.currentThread().interrupt();
11282             return;
11283           }
11284         }
11285       } finally { // restore the original priority
11286         Thread.currentThread().setPriority(priority);
11287       }
11288     }
11289 
11290     /**
11291      * Instruct the thread to stop masking.
11292      */
11293     public void stopMasking() {
11294       this.stop = false;
11295     }
11296   }
11297 
11298   /**
11299    * thread factory with daemon threads so the JVM exits
11300    * might want to use GrouperCallable with this
11301    * @author mchyzer
11302    *
11303    */
11304   private static class DaemonThreadFactory implements ThreadFactory {
11305 
11306     /**
11307      *
11308      */
11309     private ThreadFactory threadFactory = Executors.defaultThreadFactory();
11310 
11311     /**
11312      *
11313      */
11314     @Override
11315     public Thread newThread(Runnable r) {
11316       Thread thread = threadFactory.newThread(r);
11317       thread.setDaemon(true);
11318       return thread;
11319     }
11320 
11321   }
11322 
11323   /**
11324    * <p>Strips any of a set of characters from the start of a String.</p>
11325    *
11326    * <p>A <code>null</code> input String returns <code>null</code>.
11327    * An empty string ("") input returns the empty string.</p>
11328    *
11329    * <p>If the stripChars String is <code>null</code>, whitespace is
11330    * stripped as defined by {@link Character#isWhitespace(char)}.</p>
11331    *
11332    * <pre>
11333    * StringUtils.stripStart(null, *)          = null
11334    * StringUtils.stripStart("", *)            = ""
11335    * StringUtils.stripStart("abc", "")        = "abc"
11336    * StringUtils.stripStart("abc", null)      = "abc"
11337    * StringUtils.stripStart("  abc", null)    = "abc"
11338    * StringUtils.stripStart("abc  ", null)    = "abc  "
11339    * StringUtils.stripStart(" abc ", null)    = "abc "
11340    * StringUtils.stripStart("yxabc  ", "xyz") = "abc  "
11341    * </pre>
11342    *
11343    * @param str  the String to remove characters from, may be null
11344    * @param stripChars  the characters to remove, null treated as whitespace
11345    * @return the stripped String, <code>null</code> if null String input
11346    */
11347   public static String stripStart(String str, String stripChars) {
11348     int strLen;
11349     if (str == null || (strLen = str.length()) == 0) {
11350       return str;
11351     }
11352     int start = 0;
11353     if (stripChars == null) {
11354       while ((start != strLen) && Character.isWhitespace(str.charAt(start))) {
11355         start++;
11356       }
11357     } else if (stripChars.length() == 0) {
11358       return str;
11359     } else {
11360       while ((start != strLen) && (stripChars.indexOf(str.charAt(start)) != -1)) {
11361         start++;
11362       }
11363     }
11364     return str.substring(start);
11365   }
11366 
11367   /**
11368    * <p>Strips any of a set of characters from the end of a String.</p>
11369    *
11370    * <p>A <code>null</code> input String returns <code>null</code>.
11371    * An empty string ("") input returns the empty string.</p>
11372    *
11373    * <p>If the stripChars String is <code>null</code>, whitespace is
11374    * stripped as defined by {@link Character#isWhitespace(char)}.</p>
11375    *
11376    * <pre>
11377    * StringUtils.stripEnd(null, *)          = null
11378    * StringUtils.stripEnd("", *)            = ""
11379    * StringUtils.stripEnd("abc", "")        = "abc"
11380    * StringUtils.stripEnd("abc", null)      = "abc"
11381    * StringUtils.stripEnd("  abc", null)    = "  abc"
11382    * StringUtils.stripEnd("abc  ", null)    = "abc"
11383    * StringUtils.stripEnd(" abc ", null)    = " abc"
11384    * StringUtils.stripEnd("  abcyx", "xyz") = "  abc"
11385    * </pre>
11386    *
11387    * @param str  the String to remove characters from, may be null
11388    * @param stripChars  the characters to remove, null treated as whitespace
11389    * @deprecated use stripSuffix instead
11390    * @return the stripped String, <code>null</code> if null String input
11391    */
11392   @Deprecated
11393   public static String stripEnd(String str, String stripChars) {
11394     int end;
11395     if (str == null || (end = str.length()) == 0) {
11396       return str;
11397     }
11398 
11399     if (stripChars == null) {
11400       while ((end != 0) && Character.isWhitespace(str.charAt(end - 1))) {
11401         end--;
11402       }
11403     } else if (stripChars.length() == 0) {
11404       return str;
11405     } else {
11406       while ((end != 0) && (stripChars.indexOf(str.charAt(end - 1)) != -1)) {
11407         end--;
11408       }
11409     }
11410     return str.substring(0, end);
11411   }
11412 
11413   /**
11414    * The empty String <code>""</code>.
11415    * @since 2.0
11416    */
11417   public static final String EMPTY = "";
11418 
11419   /**
11420    * Represents a failed index search.
11421    * @since 2.1
11422    */
11423   public static final int INDEX_NOT_FOUND = -1;
11424 
11425   /**
11426    * <p>The maximum size to which the padding constant(s) can expand.</p>
11427    */
11428   private static final int PAD_LIMIT = 8192;
11429 
11430   /**
11431    * <p>An array of <code>String</code>s used for padding.</p>
11432    *
11433    * <p>Used for efficient space padding. The length of each String expands as needed.</p>
11434    */
11435   private static final String[] PADDING = new String[Character.MAX_VALUE];
11436 
11437   static {
11438     // space padding is most common, start with 64 chars
11439     PADDING[32] = "                                                                ";
11440   }
11441 
11442   /**
11443    * <p>Repeat a String <code>repeat</code> times to form a
11444    * new String.</p>
11445    *
11446    * <pre>
11447    * StringUtils.repeat(null, 2) = null
11448    * StringUtils.repeat("", 0)   = ""
11449    * StringUtils.repeat("", 2)   = ""
11450    * StringUtils.repeat("a", 3)  = "aaa"
11451    * StringUtils.repeat("ab", 2) = "abab"
11452    * StringUtils.repeat("a", -2) = ""
11453    * </pre>
11454    *
11455    * @param str  the String to repeat, may be null
11456    * @param repeat  number of times to repeat str, negative treated as zero
11457    * @return a new String consisting of the original String repeated,
11458    *  <code>null</code> if null String input
11459    */
11460   public static String repeat(String str, int repeat) {
11461     // Performance tuned for 2.0 (JDK1.4)
11462 
11463     if (str == null) {
11464       return null;
11465     }
11466     if (repeat <= 0) {
11467       return EMPTY;
11468     }
11469     int inputLength = str.length();
11470     if (repeat == 1 || inputLength == 0) {
11471       return str;
11472     }
11473     if (inputLength == 1 && repeat <= PAD_LIMIT) {
11474       return padding(repeat, str.charAt(0));
11475     }
11476 
11477     int outputLength = inputLength * repeat;
11478     switch (inputLength) {
11479       case 1:
11480         char ch = str.charAt(0);
11481         char[] output1 = new char[outputLength];
11482         for (int i = repeat - 1; i >= 0; i--) {
11483           output1[i] = ch;
11484         }
11485         return new String(output1);
11486       case 2:
11487         char ch0 = str.charAt(0);
11488         char ch1 = str.charAt(1);
11489         char[] output2 = new char[outputLength];
11490         for (int i = repeat * 2 - 2; i >= 0; i--, i--) {
11491           output2[i] = ch0;
11492           output2[i + 1] = ch1;
11493         }
11494         return new String(output2);
11495       default:
11496         StringBuffer buf = new StringBuffer(outputLength);
11497         for (int i = 0; i < repeat; i++) {
11498           buf.append(str);
11499         }
11500         return buf.toString();
11501     }
11502   }
11503 
11504   /**
11505    * <p>Returns padding using the specified delimiter repeated
11506    * to a given length.</p>
11507    *
11508    * <pre>
11509    * StringUtils.padding(0, 'e')  = ""
11510    * StringUtils.padding(3, 'e')  = "eee"
11511    * StringUtils.padding(-2, 'e') = IndexOutOfBoundsException
11512    * </pre>
11513    *
11514    * @param repeat  number of times to repeat delim
11515    * @param padChar  character to repeat
11516    * @return String with repeated character
11517    * @throws IndexOutOfBoundsException if <code>repeat &lt; 0</code>
11518    */
11519   private static String padding(int repeat, char padChar) {
11520     // be careful of synchronization in this method
11521     // we are assuming that get and set from an array index is atomic
11522     String pad = PADDING[padChar];
11523     if (pad == null) {
11524       pad = String.valueOf(padChar);
11525     }
11526     while (pad.length() < repeat) {
11527       pad = pad.concat(pad);
11528     }
11529     PADDING[padChar] = pad;
11530     return pad.substring(0, repeat);
11531   }
11532 
11533   /**
11534    * <p>Right pad a String with spaces (' ').</p>
11535    *
11536    * <p>The String is padded to the size of <code>size</code>.</p>
11537    *
11538    * <pre>
11539    * StringUtils.rightPad(null, *)   = null
11540    * StringUtils.rightPad("", 3)     = "   "
11541    * StringUtils.rightPad("bat", 3)  = "bat"
11542    * StringUtils.rightPad("bat", 5)  = "bat  "
11543    * StringUtils.rightPad("bat", 1)  = "bat"
11544    * StringUtils.rightPad("bat", -1) = "bat"
11545    * </pre>
11546    *
11547    * @param str  the String to pad out, may be null
11548    * @param size  the size to pad to
11549    * @return right padded String or original String if no padding is necessary,
11550    *  <code>null</code> if null String input
11551    */
11552   public static String rightPad(String str, int size) {
11553     return rightPad(str, size, ' ');
11554   }
11555 
11556   /**
11557    * <p>Right pad a String with a specified character.</p>
11558    *
11559    * <p>The String is padded to the size of <code>size</code>.</p>
11560    *
11561    * <pre>
11562    * StringUtils.rightPad(null, *, *)     = null
11563    * StringUtils.rightPad("", 3, 'z')     = "zzz"
11564    * StringUtils.rightPad("bat", 3, 'z')  = "bat"
11565    * StringUtils.rightPad("bat", 5, 'z')  = "batzz"
11566    * StringUtils.rightPad("bat", 1, 'z')  = "bat"
11567    * StringUtils.rightPad("bat", -1, 'z') = "bat"
11568    * </pre>
11569    *
11570    * @param str  the String to pad out, may be null
11571    * @param size  the size to pad to
11572    * @param padChar  the character to pad with
11573    * @return right padded String or original String if no padding is necessary,
11574    *  <code>null</code> if null String input
11575    * @since 2.0
11576    */
11577   public static String rightPad(String str, int size, char padChar) {
11578     if (str == null) {
11579       return null;
11580     }
11581     int pads = size - str.length();
11582     if (pads <= 0) {
11583       return str; // returns original String when possible
11584     }
11585     if (pads > PAD_LIMIT) {
11586       return rightPad(str, size, String.valueOf(padChar));
11587     }
11588     return str.concat(padding(pads, padChar));
11589   }
11590 
11591   /**
11592    * <p>Right pad a String with a specified String.</p>
11593    *
11594    * <p>The String is padded to the size of <code>size</code>.</p>
11595    *
11596    * <pre>
11597    * StringUtils.rightPad(null, *, *)      = null
11598    * StringUtils.rightPad("", 3, "z")      = "zzz"
11599    * StringUtils.rightPad("bat", 3, "yz")  = "bat"
11600    * StringUtils.rightPad("bat", 5, "yz")  = "batyz"
11601    * StringUtils.rightPad("bat", 8, "yz")  = "batyzyzy"
11602    * StringUtils.rightPad("bat", 1, "yz")  = "bat"
11603    * StringUtils.rightPad("bat", -1, "yz") = "bat"
11604    * StringUtils.rightPad("bat", 5, null)  = "bat  "
11605    * StringUtils.rightPad("bat", 5, "")    = "bat  "
11606    * </pre>
11607    *
11608    * @param str  the String to pad out, may be null
11609    * @param size  the size to pad to
11610    * @param padStr  the String to pad with, null or empty treated as single space
11611    * @return right padded String or original String if no padding is necessary,
11612    *  <code>null</code> if null String input
11613    */
11614   public static String rightPad(String str, int size, String padStr) {
11615     if (str == null) {
11616       return null;
11617     }
11618     if (isEmpty(padStr)) {
11619       padStr = " ";
11620     }
11621     int padLen = padStr.length();
11622     int strLen = str.length();
11623     int pads = size - strLen;
11624     if (pads <= 0) {
11625       return str; // returns original String when possible
11626     }
11627     if (padLen == 1 && pads <= PAD_LIMIT) {
11628       return rightPad(str, size, padStr.charAt(0));
11629     }
11630 
11631     if (pads == padLen) {
11632       return str.concat(padStr);
11633     } else if (pads < padLen) {
11634       return str.concat(padStr.substring(0, pads));
11635     } else {
11636       char[] padding = new char[pads];
11637       char[] padChars = padStr.toCharArray();
11638       for (int i = 0; i < pads; i++) {
11639         padding[i] = padChars[i % padLen];
11640       }
11641       return str.concat(new String(padding));
11642     }
11643   }
11644 
11645   /**
11646    * <p>Left pad a String with spaces (' ').</p>
11647    *
11648    * <p>The String is padded to the size of <code>size<code>.</p>
11649    *
11650    * <pre>
11651    * StringUtils.leftPad(null, *)   = null
11652    * StringUtils.leftPad("", 3)     = "   "
11653    * StringUtils.leftPad("bat", 3)  = "bat"
11654    * StringUtils.leftPad("bat", 5)  = "  bat"
11655    * StringUtils.leftPad("bat", 1)  = "bat"
11656    * StringUtils.leftPad("bat", -1) = "bat"
11657    * </pre>
11658    *
11659    * @param str  the String to pad out, may be null
11660    * @param size  the size to pad to
11661    * @return left padded String or original String if no padding is necessary,
11662    *  <code>null</code> if null String input
11663    */
11664   public static String leftPad(String str, int size) {
11665     return leftPad(str, size, ' ');
11666   }
11667 
11668   /**
11669    * <p>Left pad a String with a specified character.</p>
11670    *
11671    * <p>Pad to a size of <code>size</code>.</p>
11672    *
11673    * <pre>
11674    * StringUtils.leftPad(null, *, *)     = null
11675    * StringUtils.leftPad("", 3, 'z')     = "zzz"
11676    * StringUtils.leftPad("bat", 3, 'z')  = "bat"
11677    * StringUtils.leftPad("bat", 5, 'z')  = "zzbat"
11678    * StringUtils.leftPad("bat", 1, 'z')  = "bat"
11679    * StringUtils.leftPad("bat", -1, 'z') = "bat"
11680    * </pre>
11681    *
11682    * @param str  the String to pad out, may be null
11683    * @param size  the size to pad to
11684    * @param padChar  the character to pad with
11685    * @return left padded String or original String if no padding is necessary,
11686    *  <code>null</code> if null String input
11687    * @since 2.0
11688    */
11689   public static String leftPad(String str, int size, char padChar) {
11690     if (str == null) {
11691       return null;
11692     }
11693     int pads = size - str.length();
11694     if (pads <= 0) {
11695       return str; // returns original String when possible
11696     }
11697     if (pads > PAD_LIMIT) {
11698       return leftPad(str, size, String.valueOf(padChar));
11699     }
11700     return padding(pads, padChar).concat(str);
11701   }
11702 
11703   /**
11704    * <p>Left pad a String with a specified String.</p>
11705    *
11706    * <p>Pad to a size of <code>size</code>.</p>
11707    *
11708    * <pre>
11709    * StringUtils.leftPad(null, *, *)      = null
11710    * StringUtils.leftPad("", 3, "z")      = "zzz"
11711    * StringUtils.leftPad("bat", 3, "yz")  = "bat"
11712    * StringUtils.leftPad("bat", 5, "yz")  = "yzbat"
11713    * StringUtils.leftPad("bat", 8, "yz")  = "yzyzybat"
11714    * StringUtils.leftPad("bat", 1, "yz")  = "bat"
11715    * StringUtils.leftPad("bat", -1, "yz") = "bat"
11716    * StringUtils.leftPad("bat", 5, null)  = "  bat"
11717    * StringUtils.leftPad("bat", 5, "")    = "  bat"
11718    * </pre>
11719    *
11720    * @param str  the String to pad out, may be null
11721    * @param size  the size to pad to
11722    * @param padStr  the String to pad with, null or empty treated as single space
11723    * @return left padded String or original String if no padding is necessary,
11724    *  <code>null</code> if null String input
11725    */
11726   public static String leftPad(String str, int size, String padStr) {
11727     if (str == null) {
11728       return null;
11729     }
11730     if (isEmpty(padStr)) {
11731       padStr = " ";
11732     }
11733     int padLen = padStr.length();
11734     int strLen = str.length();
11735     int pads = size - strLen;
11736     if (pads <= 0) {
11737       return str; // returns original String when possible
11738     }
11739     if (padLen == 1 && pads <= PAD_LIMIT) {
11740       return leftPad(str, size, padStr.charAt(0));
11741     }
11742 
11743     if (pads == padLen) {
11744       return padStr.concat(str);
11745     } else if (pads < padLen) {
11746       return padStr.substring(0, pads).concat(str);
11747     } else {
11748       char[] padding = new char[pads];
11749       char[] padChars = padStr.toCharArray();
11750       for (int i = 0; i < pads; i++) {
11751         padding[i] = padChars[i % padLen];
11752       }
11753       return new String(padding).concat(str);
11754     }
11755   }
11756 
11757   /**
11758    * <p>Gets the substring before the first occurrence of a separator.
11759    * The separator is not returned.</p>
11760    *
11761    * <p>A <code>null</code> string input will return <code>null</code>.
11762    * An empty ("") string input will return the empty string.
11763    * A <code>null</code> separator will return the input string.</p>
11764    *
11765    * <pre>
11766    * StringUtils.substringBefore(null, *)      = null
11767    * StringUtils.substringBefore("", *)        = ""
11768    * StringUtils.substringBefore("abc", "a")   = ""
11769    * StringUtils.substringBefore("abcba", "b") = "a"
11770    * StringUtils.substringBefore("abc", "c")   = "ab"
11771    * StringUtils.substringBefore("abc", "d")   = "abc"
11772    * StringUtils.substringBefore("abc", "")    = ""
11773    * StringUtils.substringBefore("abc", null)  = "abc"
11774    * </pre>
11775    *
11776    * @param str  the String to get a substring from, may be null
11777    * @param separator  the String to search for, may be null
11778    * @return the substring before the first occurrence of the separator,
11779    *  <code>null</code> if null String input
11780    * @since 2.0
11781    */
11782   public static String substringBefore(String str, String separator) {
11783     if (isEmpty(str) || separator == null) {
11784       return str;
11785     }
11786     if (separator.length() == 0) {
11787       return EMPTY;
11788     }
11789     int pos = str.indexOf(separator);
11790     if (pos == -1) {
11791       return str;
11792     }
11793     return str.substring(0, pos);
11794   }
11795 
11796   /**
11797    * <p>Gets the substring after the first occurrence of a separator.
11798    * The separator is not returned.</p>
11799    *
11800    * <p>A <code>null</code> string input will return <code>null</code>.
11801    * An empty ("") string input will return the empty string.
11802    * A <code>null</code> separator will return the empty string if the
11803    * input string is not <code>null</code>.</p>
11804    *
11805    * <pre>
11806    * StringUtils.substringAfter(null, *)      = null
11807    * StringUtils.substringAfter("", *)        = ""
11808    * StringUtils.substringAfter(*, null)      = ""
11809    * StringUtils.substringAfter("abc", "a")   = "bc"
11810    * StringUtils.substringAfter("abcba", "b") = "cba"
11811    * StringUtils.substringAfter("abc", "c")   = ""
11812    * StringUtils.substringAfter("abc", "d")   = ""
11813    * StringUtils.substringAfter("abc", "")    = "abc"
11814    * </pre>
11815    *
11816    * @param str  the String to get a substring from, may be null
11817    * @param separator  the String to search for, may be null
11818    * @return the substring after the first occurrence of the separator,
11819    *  <code>null</code> if null String input
11820    * @since 2.0
11821    */
11822   public static String substringAfter(String str, String separator) {
11823     if (isEmpty(str)) {
11824       return str;
11825     }
11826     if (separator == null) {
11827       return EMPTY;
11828     }
11829     int pos = str.indexOf(separator);
11830     if (pos == -1) {
11831       return EMPTY;
11832     }
11833     return str.substring(pos + separator.length());
11834   }
11835 
11836   /**
11837    * <p>Gets the substring before the last occurrence of a separator.
11838    * The separator is not returned.</p>
11839    *
11840    * <p>A <code>null</code> string input will return <code>null</code>.
11841    * An empty ("") string input will return the empty string.
11842    * An empty or <code>null</code> separator will return the input string.</p>
11843    *
11844    * <pre>
11845    * StringUtils.substringBeforeLast(null, *)      = null
11846    * StringUtils.substringBeforeLast("", *)        = ""
11847    * StringUtils.substringBeforeLast("abcba", "b") = "abc"
11848    * StringUtils.substringBeforeLast("abc", "c")   = "ab"
11849    * StringUtils.substringBeforeLast("a", "a")     = ""
11850    * StringUtils.substringBeforeLast("a", "z")     = "a"
11851    * StringUtils.substringBeforeLast("a", null)    = "a"
11852    * StringUtils.substringBeforeLast("a", "")      = "a"
11853    * </pre>
11854    *
11855    * @param str  the String to get a substring from, may be null
11856    * @param separator  the String to search for, may be null
11857    * @return the substring before the last occurrence of the separator,
11858    *  <code>null</code> if null String input
11859    * @since 2.0
11860    */
11861   public static String substringBeforeLast(String str, String separator) {
11862     if (isEmpty(str) || isEmpty(separator)) {
11863       return str;
11864     }
11865     int pos = str.lastIndexOf(separator);
11866     if (pos == -1) {
11867       return str;
11868     }
11869     return str.substring(0, pos);
11870   }
11871 
11872   /**
11873    * <p>Gets the substring after the last occurrence of a separator.
11874    * The separator is not returned.</p>
11875    *
11876    * <p>A <code>null</code> string input will return <code>null</code>.
11877    * An empty ("") string input will return the empty string.
11878    * An empty or <code>null</code> separator will return the empty string if
11879    * the input string is not <code>null</code>.</p>
11880    *
11881    * <pre>
11882    * StringUtils.substringAfterLast(null, *)      = null
11883    * StringUtils.substringAfterLast("", *)        = ""
11884    * StringUtils.substringAfterLast(*, "")        = ""
11885    * StringUtils.substringAfterLast(*, null)      = ""
11886    * StringUtils.substringAfterLast("abc", "a")   = "bc"
11887    * StringUtils.substringAfterLast("abcba", "b") = "a"
11888    * StringUtils.substringAfterLast("abc", "c")   = ""
11889    * StringUtils.substringAfterLast("a", "a")     = ""
11890    * StringUtils.substringAfterLast("a", "z")     = ""
11891    * </pre>
11892    *
11893    * @param str  the String to get a substring from, may be null
11894    * @param separator  the String to search for, may be null
11895    * @return the substring after the last occurrence of the separator,
11896    *  <code>null</code> if null String input
11897    * @since 2.0
11898    */
11899   public static String substringAfterLast(String str, String separator) {
11900     if (isEmpty(str)) {
11901       return str;
11902     }
11903     if (isEmpty(separator)) {
11904       return EMPTY;
11905     }
11906     int pos = str.lastIndexOf(separator);
11907     if (pos == -1 || pos == (str.length() - separator.length())) {
11908       return EMPTY;
11909     }
11910     return str.substring(pos + separator.length());
11911   }
11912 
11913   /**
11914    * wait for input
11915    */
11916   public static void waitForInput() {
11917     System.out.print("Press enter to continue: ");
11918     BufferedReader stdin = null;
11919 
11920     try {
11921       stdin = new BufferedReader(new InputStreamReader(System.in));
11922       stdin.readLine();
11923     } catch (Exception e) {
11924       throw new RuntimeException(e);
11925     }
11926 
11927   }
11928 
11929   /**
11930    * find an object (or objects) in a collection based on fields
11931    * @param <T>
11932    * @param collection
11933    * @param propertyNames
11934    * @param propertyValues
11935    * @return the object(s) or empty list if cant find
11936    */
11937   public static <T> T retrieveByProperties(Collection<T> collection,
11938       List<String> propertyNames, List<Object> propertyValues) {
11939     List<T> list = retrieveListByProperties(collection, propertyNames, propertyValues);
11940     return listPopOne(list);
11941   }
11942 
11943   /**
11944    * find an object (or objects) in a collection based on fields
11945    * @param <T>
11946    * @param collection
11947    * @param propertyName
11948    * @param propertyValue
11949    * @return the object(s) or empty list if cant find
11950    */
11951   public static <T> T retrieveByProperty(Collection<T> collection,
11952       String propertyName, Object propertyValue) {
11953     List<T> list = retrieveListByProperty(collection, propertyName, propertyValue);
11954     return listPopOne(list);
11955   }
11956 
11957   /**
11958    * Returns the first existing parent stem of a given name.
11959    * So if the following stems exist:
11960    *   i2
11961    *   i2:test
11962    *
11963    * And you run getFirstParentStemOfName("i2:test:mystem:mygroup"),
11964    * you will get back the stem for i2:test.
11965    *
11966    * If you run getFirstParentStemOfName("test1:test2"),
11967    * you will get back the root stem.
11968    *
11969    * @param name
11970    * @return Stem
11971    */
11972   public static Stem getFirstParentStemOfName(String name) {
11973     String parent = parentStemNameFromName(name);
11974 
11975     if (parent == null || parent.equals(name)) {
11976       return StemFinder.findRootStem(GrouperSession.staticGrouperSession()
11977           .internal_getRootSession());
11978     }
11979 
11980     Stem stem = StemFinder.findByName(GrouperSession.staticGrouperSession()
11981         .internal_getRootSession(), parent, false);
11982     if (stem != null) {
11983       return stem;
11984     }
11985 
11986     return getFirstParentStemOfName(parent);
11987   }
11988 
11989   /**
11990    * find an object (or objects) in a collection based on fields
11991    * @param <T>
11992    * @param collection
11993    * @param propertyNames
11994    * @param propertyValues
11995    * @return the object(s) or empty list if cant find
11996    */
11997   public static <T> List<T> retrieveListByProperties(Collection<T> collection,
11998       List<String> propertyNames, List<Object> propertyValues) {
11999 
12000     int fieldNameLength = propertyNames.size();
12001 
12002     List<T> result = new ArrayList<T>();
12003 
12004     assertion(fieldNameLength == propertyValues.size(), "Problem: " + fieldNameLength + " != " + propertyValues.size());
12005 
12006     OUTER: for (T object : collection) {
12007       //loop through fields and values
12008       for (int i=0;i<fieldNameLength;i++) {
12009         Object propertyValue = propertyValue(object, propertyNames.get(i));
12010         if (!equals(propertyValue, propertyValues.get(i))) {
12011           continue OUTER;
12012         }
12013       }
12014       //if we got this far, then its a match
12015       result.add(object);
12016 
12017     }
12018     //if we havent found one, we done
12019     return result;
12020   }
12021 
12022   /**
12023    * find an object (or objects) in a collection based on fields
12024    * @param <T>
12025    * @param collection
12026    * @param propertyName
12027    * @param propertyValue
12028    * @return the object(s) or empty list if cant find
12029    */
12030   public static <T> List<T> retrieveListByProperty(Collection<T> collection,
12031       String propertyName, Object propertyValue) {
12032 
12033     List<T> result = new ArrayList<T>();
12034 
12035     OUTER: for (T object : collection) {
12036       if (object == null) {
12037         continue;
12038       }
12039       Object currentPropertyValue = propertyValue(object, propertyName);
12040       if (!equals(currentPropertyValue, propertyValue)) {
12041         continue OUTER;
12042       }
12043       //if we got this far, then its a match
12044       result.add(object);
12045 
12046     }
12047     //if we havent found one, we done
12048     return result;
12049   }
12050 
12051   /**
12052    * Return the zero element of the list, if it exists, null if the list is empty.
12053    * If there is more than one element in the list, an exception is thrown.
12054    * @param <T>
12055    * @param list is the container of objects to get the first of.
12056    * @return the first object, null, or exception.
12057    */
12058   public static <T> T listPopOne(List<T> list) {
12059     int size = length(list);
12060     if (size == 1) {
12061       return list.get(0);
12062     } else if (size == 0) {
12063       return null;
12064     }
12065     throw new RuntimeException("More than one object of type " + className(list.get(0))
12066         + " was returned when only one was expected. (size:" + size +")" );
12067   }
12068 
12069   /**
12070    * Return the zero element of the set, if it exists, null if the list is empty.
12071    * If there is more than one element in the list, an exception is thrown.
12072    * @param <T>
12073    * @param set is the container of objects to get the first of.
12074    * @return the first object, null, or exception.
12075    */
12076   public static <T> T setPopOne(Set<T> set) {
12077     int size = length(set);
12078     if (size == 1) {
12079       return set.iterator().next();
12080     } else if (size == 0) {
12081       return null;
12082     }
12083     throw new RuntimeException("More than one object of type " + className(set.iterator().next())
12084         + " was returned when only one was expected. (size:" + size +")" );
12085   }
12086 
12087   /**
12088    * Return the zero element of the list, if it exists, null if the list is empty.
12089    * If there is more than one element in the list, an exception is thrown.
12090    * @param <T>
12091    * @param collection is the container of objects to get the first of.
12092    * @param exceptionIfMoreThanOne will throw exception if there is more than one item in list
12093    * @return the first object, null, or exception.
12094    */
12095   public static <T> T collectionPopOne(Collection<T> collection, boolean exceptionIfMoreThanOne) {
12096     int size = length(collection);
12097     if (size > 1 && exceptionIfMoreThanOne) {
12098       throw new RuntimeException("More than one object of type " + className(get(collection, 0))
12099           + " was returned when only one was expected. (size:" + size +")" );
12100     }
12101     if (size == 0) {
12102       return null;
12103     }
12104     return collection.iterator().next();
12105   }
12106 
12107   /** array for converting HTML to string */
12108   private static final String[] XML_SEARCH_NO_SINGLE = new String[]{"&","<",">","\""};
12109 
12110   /** array for converting HTML to string */
12111   private static final String[] XML_REPLACE_NO_SINGLE = new String[]{"&amp;","&lt;","&gt;","&quot;"};
12112 
12113 
12114   /**
12115    * Convert an XML string to HTML to display on the screen
12116    * 
12117    * @param input
12118    *          is the XML to convert
12119    * @param isEscape true to escape chars, false to unescape
12120    * 
12121    * @return the HTML converted string
12122    */
12123   public static String xmlEscape(String input) {
12124     return xmlEscape(input, true);
12125   }
12126 
12127   /**
12128    * Convert an XML string to HTML to display on the screen
12129    *
12130    * @param input
12131    *          is the XML to convert
12132    * @param isEscape true to escape chars, false to unescape
12133    *
12134    * @return the HTML converted string
12135    */
12136   public static String xmlEscape(String input, boolean isEscape) {
12137     if (isEscape) {
12138       return replace(input, XML_SEARCH_NO_SINGLE, XML_REPLACE_NO_SINGLE);
12139     }
12140     return replace(input, XML_REPLACE_NO_SINGLE, XML_SEARCH_NO_SINGLE);
12141   }
12142 
12143   /**
12144    *
12145    * @param writer
12146    * @param attributeName
12147    * @param attributeValue
12148    */
12149   public static void xmlAttribute(Writer writer, String attributeName, String attributeValue) {
12150     try {
12151       writer.write(" ");
12152       writer.write(attributeName);
12153       writer.write("=\"");
12154       String escapedValue = xmlEscape(attributeValue, true);
12155       writer.write(escapedValue);
12156       writer.write("\"");
12157     } catch (IOException ioe) {
12158       throw new RuntimeException(ioe);
12159     }
12160   }
12161 
12162   /**
12163    * see if the object has string fields with members in them (by memberUuid), and if so, export that member
12164    * @param object
12165    * @param stringSubstituteMap
12166    * @return if altered or not
12167    */
12168   public static boolean substituteStrings(Map<String, String> stringSubstituteMap,
12169       Object object) {
12170 
12171     Set<String> fieldNames = stringFieldNames(object.getClass());
12172 
12173     boolean altered = false;
12174 
12175     //go through all the fields in the object
12176     for (String fieldName : fieldNames) {
12177       String value = (String)fieldValue(object, fieldName);
12178 
12179       //see if the value is a member id which has not been exported
12180       if (!StringUtils.isBlank(value) && stringSubstituteMap.containsKey(value)) {
12181         //assign the substitution
12182         assignField(object, fieldName, stringSubstituteMap.get(value));
12183         altered = true;
12184       }
12185     }
12186     return altered;
12187   }
12188 
12189   /** map of class to the list of fields which are string type */
12190   public static Map<Class<?>, Set<String>> stringFieldNames = new HashMap<Class<?>, Set<String>>();
12191 
12192 
12193   /**
12194    * get the list of string field names based on class, and cache this
12195    * @param theClass
12196    * @return the set of field names
12197    */
12198   public static Set<String> stringFieldNames(Class<?> theClass) {
12199 
12200     if (!stringFieldNames.containsKey(theClass)) {
12201       stringFieldNames.put(theClass, fieldNames(theClass, String.class, false));
12202     }
12203     return stringFieldNames.get(theClass);
12204   }
12205 
12206     /**
12207    * if theString is not blank, apppend it to the result, and if appending,
12208    * @param result to append to
12209    * add a prefix and suffix (if not null)
12210    * @param theStringOrArrayOrList is a string, array, list, or set of strings
12211    * @return true if something appended, false if not
12212    */
12213   public static boolean appendIfNotBlank(StringBuilder result,
12214       Object theStringOrArrayOrList) {
12215     return appendIfNotBlank(result, null, theStringOrArrayOrList, null);
12216   }
12217 
12218   /**
12219    * <pre>
12220    * append a string to another string if both not blank, with separator.  trim to empty everything
12221    * </pre>
12222    * @param string
12223    * @param separator
12224    * @param suffix
12225    * @return the resulting string or blank if nothing
12226    */
12227   public static String appendIfNotBlankString(String string, String separator, String suffix) {
12228 
12229     string = StringUtils.trimToEmpty(string);
12230     suffix = StringUtils.trimToEmpty(suffix);
12231 
12232     boolean stringIsBlank = StringUtils.isBlank(string);
12233     boolean suffixIsBlank = StringUtils.isBlank(suffix);
12234 
12235     if (stringIsBlank && suffixIsBlank) {
12236       return "";
12237     }
12238 
12239     if (stringIsBlank) {
12240       return suffix;
12241     }
12242 
12243     if (suffixIsBlank) {
12244       return string;
12245     }
12246 
12247     return string + separator + suffix;
12248 
12249   }
12250 
12251   /**
12252    * <pre>
12253    * append a prefix to another string if both not blank.  trim to empty everything
12254    * i.e. appendPrefixIfStringNotBlank("[]", " - ", "a")   returns [] - a
12255    * i.e. appendPrefixIfStringNotBlank("", " - ", "a")   returns a
12256    * i.e. appendPrefixIfStringNotBlank("[]", " - ", "")   returns ""
12257    * i.e. appendPrefixIfStringNotBlank("", " - ", "")   returns ""
12258    * </pre>
12259    * @param prefix
12260    * @param separator
12261    * @param string
12262    * @return the resulting string or blank if nothing
12263    */
12264   public static String appendPrefixIfStringNotBlank(String prefix, String separator, String string) {
12265     
12266     string = StringUtils.trimToEmpty(string);
12267     prefix = StringUtils.trimToEmpty(prefix);
12268     
12269     boolean stringIsBlank = StringUtils.isBlank(string);
12270     boolean prefixIsBlank = StringUtils.isBlank(prefix);
12271 
12272     if (stringIsBlank) {
12273       return "";
12274     }
12275 
12276     if (prefixIsBlank) {
12277       return string;
12278     }
12279     
12280     return prefix + separator + string;
12281     
12282   }
12283 
12284   /**
12285    * if theString is not Blank, apppend it to the result, and if appending,
12286    * add a prefix (if not null)
12287    * @param result to append to
12288    * @param prefix
12289    * @param theStringOrArrayOrList is a string, array, list, or set of strings
12290    * @return true if something appended, false if not
12291    */
12292   public static boolean appendIfNotBlank(StringBuilder result,
12293       String prefix, Object theStringOrArrayOrList) {
12294     return appendIfNotBlank(result, prefix, theStringOrArrayOrList, null);
12295   }
12296 
12297   /**
12298    * if theString is not Blank, apppend it to the result, and if appending,
12299    * add a prefix and suffix (if not null)
12300    * @param result to append to, assumed to be not null
12301    * @param prefix
12302    * @param theStringOrArrayOrList is a string, array, list, or set of strings
12303    * @param suffix
12304    * @return true if anything appended, false if not
12305    */
12306   public static boolean appendIfNotBlank(StringBuilder result,
12307       String prefix, Object theStringOrArrayOrList, String suffix) {
12308     return appendIfNotBlank(result, prefix, null, theStringOrArrayOrList, suffix);
12309   }
12310 
12311   /**
12312    * if theString is not Blank, apppend it to the result, and if appending,
12313    * add a prefix and suffix (if not null)
12314    * @param result to append to, assumed to be not null
12315    * @param prefix prepend this prefix always (when a result not empty).  Will be after the other prefix
12316    * @param prefixIfNotBlank prepend this prefix if the result is not empty
12317    * @param theStringOrArrayOrList is a string, array, list, or set of strings
12318    * @param suffix
12319    * @return true if anything appended, false if not
12320    */
12321   public static boolean appendIfNotBlank(StringBuilder result,
12322       String prefix, String prefixIfNotBlank, Object theStringOrArrayOrList, String suffix) {
12323     int length = length(theStringOrArrayOrList);
12324     Iterator iterator = iterator(theStringOrArrayOrList);
12325     boolean appendedAnything = false;
12326 
12327     //these could be appending spaces, so only check to see if they are empty
12328     boolean hasPrefix = !StringUtils.isEmpty(prefix);
12329     boolean hasPrefixIfNotBlank = !StringUtils.isEmpty(prefixIfNotBlank);
12330     boolean hasSuffix = !StringUtils.isEmpty(suffix);
12331     for (int i=0;i<length;i++) {
12332       String  current = (String) next(theStringOrArrayOrList, iterator, i);
12333 
12334       //only append if not empty
12335       if (!StringUtils.isBlank(current)) {
12336 
12337         //keeping track if anything changed
12338         appendedAnything = true;
12339         if (hasPrefix) {
12340           result.append(prefix);
12341         }
12342         if (hasPrefixIfNotBlank && result.length() > 0) {
12343           result.append(prefixIfNotBlank);
12344         }
12345         result.append(current);
12346         if (hasSuffix) {
12347           result.append(suffix);
12348         }
12349       }
12350     }
12351     return appendedAnything;
12352   }
12353 
12354   /**
12355      * if theString is not empty, apppend it to the result, and if appending,
12356      * @param result to append to
12357      * add a prefix and suffix (if not null)
12358      * @param theStringOrArrayOrList is a string, array, list, or set of strings
12359      * @return true if something appended, false if not
12360      */
12361     public static boolean appendIfNotEmpty(StringBuilder result,
12362         Object theStringOrArrayOrList) {
12363       return appendIfNotEmpty(result, null, theStringOrArrayOrList, null);
12364   }
12365 
12366   /**
12367    * if theString is not empty, apppend it to the result, and if appending,
12368    * add a prefix (if not null)
12369    * @param result to append to
12370    * @param prefix
12371    * @param theStringOrArrayOrList is a string, array, list, or set of strings
12372    * @return true if something appended, false if not
12373    */
12374   public static boolean appendIfNotEmpty(StringBuilder result,
12375       String prefix, Object theStringOrArrayOrList) {
12376     return appendIfNotEmpty(result, prefix, theStringOrArrayOrList, null);
12377   }
12378 
12379   /**
12380    * if theString is not empty, apppend it to the result, and if appending,
12381    * add a prefix and suffix (if not null)
12382    * @param result to append to, assumed to be not null
12383    * @param prefix
12384    * @param theStringOrArrayOrList is a string, array, list, or set of strings
12385    * @param suffix
12386    * @return true if anything appended, false if not
12387    */
12388   public static boolean appendIfNotEmpty(StringBuilder result,
12389       String prefix, Object theStringOrArrayOrList, String suffix) {
12390     return appendIfNotEmpty(result, prefix, null, theStringOrArrayOrList, suffix);
12391   }
12392 
12393   /**
12394    * if theString is not empty, apppend it to the result, and if appending,
12395    * add a prefix and suffix (if not null)
12396    * @param result to append to, assumed to be not null
12397    * @param prefix prepend this prefix always (when a result not empty).  Will be after the other prefix
12398    * @param prefixIfNotEmpty prepend this prefix if the result is not empty
12399    * @param theStringOrArrayOrList is a string, array, list, or set of strings
12400    * @param suffix
12401    * @return true if anything appended, false if not
12402    */
12403   public static boolean appendIfNotEmpty(StringBuilder result,
12404       String prefix, String prefixIfNotEmpty, Object theStringOrArrayOrList, String suffix) {
12405     int length = length(theStringOrArrayOrList);
12406     Iterator iterator = iterator(theStringOrArrayOrList);
12407     boolean appendedAnything = false;
12408     boolean hasPrefix = !StringUtils.isEmpty(prefix);
12409     boolean hasPrefixIfNotEmpty = !StringUtils.isEmpty(prefixIfNotEmpty);
12410     boolean hasSuffix = !StringUtils.isEmpty(suffix);
12411     for (int i=0;i<length;i++) {
12412       String  current = (String) next(theStringOrArrayOrList, iterator, i);
12413 
12414       //only append if not empty
12415       if (!StringUtils.isEmpty(current)) {
12416 
12417         //keeping track if anything changed
12418         appendedAnything = true;
12419         if (hasPrefix) {
12420           result.append(prefix);
12421         }
12422         if (hasPrefixIfNotEmpty && result.length() > 0) {
12423           result.append(prefixIfNotEmpty);
12424         }
12425         result.append(current);
12426         if (hasSuffix) {
12427           result.append(suffix);
12428         }
12429       }
12430     }
12431     return appendedAnything;
12432   }
12433 
12434   /**
12435    * <p>Find the index of the given object in the array.</p>
12436    *
12437    * <p>This method returns <code>-1</code> if <code>null</code> array input.</p>
12438    *
12439    * @param array  the array to search through for the object, may be <code>null</code>
12440    * @param objectToFind  the object to find, may be <code>null</code>
12441    * @return the index of the object within the array,
12442    *  <code>-1</code> if not found or <code>null</code> array input
12443    */
12444   public static int indexOf(Object[] array, Object objectToFind) {
12445     return indexOf(array, objectToFind, 0);
12446   }
12447 
12448   /**
12449    * <p>Checks if the object is in the given array.</p>
12450    *
12451    * <p>The method returns <code>false</code> if a <code>null</code> array is passed in.</p>
12452    *
12453    * @param array  the array to search through
12454    * @param objectToFind  the object to find
12455    * @return <code>true</code> if the array contains the object
12456    */
12457   public static boolean contains(Object[] array, Object objectToFind) {
12458     return indexOf(array, objectToFind) != -1;
12459   }
12460 
12461   /**
12462    * <p>Find the index of the given object in the array starting at the given index.</p>
12463    *
12464    * <p>This method returns <code>-1</code> if <code>null</code> array input.</p>
12465    *
12466    * <p>A negative startIndex is treated as zero. A startIndex larger than the array
12467    * length will return <code>-1</code>.</p>
12468    *
12469    * @param array  the array to search through for the object, may be <code>null</code>
12470    * @param objectToFind  the object to find, may be <code>null</code>
12471    * @param startIndex  the index to start searching at
12472    * @return the index of the object within the array starting at the index,
12473    *  <code>-1</code> if not found or <code>null</code> array input
12474    */
12475   public static int indexOf(Object[] array, Object objectToFind, int startIndex) {
12476     if (array == null) {
12477       return -1;
12478     }
12479     if (startIndex < 0) {
12480       startIndex = 0;
12481     }
12482     if (objectToFind == null) {
12483       for (int i = startIndex; i < array.length; i++) {
12484         if (array[i] == null) {
12485           return i;
12486         }
12487       }
12488     } else {
12489       for (int i = startIndex; i < array.length; i++) {
12490         if (objectToFind.equals(array[i])) {
12491           return i;
12492         }
12493       }
12494     }
12495     return -1;
12496   }
12497 
12498   /**
12499    * Return the zero element of the array, if it exists, null if the array is empty.
12500    * If there is more than one element in the list, an exception is thrown.
12501    * @param <T>
12502    * @param array is the container of objects to get the first of.
12503    * @return the first object, null, or exception.
12504    */
12505   public static <T> T arrayPopOne(T[] array) {
12506     int size = length(array);
12507     if (size == 1) {
12508       return array[0];
12509     } else if (size == 0) {
12510       return null;
12511     }
12512     throw new RuntimeException("More than one object of type " + className(array[0])
12513         + " was returned when only one was expected. (size:" + size +")" );
12514   }
12515 
12516   /**
12517    * Note, this is
12518    * web service format string
12519    */
12520   private static final String WS_DATE_FORMAT = "yyyy/MM/dd HH:mm:ss.SSS";
12521 
12522   /**
12523    * Note, this is
12524    * web service format string
12525    */
12526   private static final String WS_DATE_FORMAT2 = "yyyy/MM/dd_HH:mm:ss.SSS";
12527 
12528   /**
12529    * convert a date to a string using the standard web service pattern
12530    * yyyy/MM/dd HH:mm:ss.SSS Note that HH is 0-23
12531    *
12532    * @param date
12533    * @return the string, or null if the date is null
12534    */
12535   public static String dateToString(Date date) {
12536     if (date == null) {
12537       return null;
12538     }
12539     SimpleDateFormat simpleDateFormat = new SimpleDateFormat(WS_DATE_FORMAT);
12540     return simpleDateFormat.format(date);
12541   }
12542 
12543   /**
12544    * convert a string to a date using the standard web service pattern Note
12545    * that HH is 0-23
12546    *
12547    * @param dateString
12548    * @return the string, or null if the date was null
12549    */
12550   public static Date stringToDate(String dateString) {
12551     if (isBlank(dateString)) {
12552       return null;
12553     }
12554     SimpleDateFormat simpleDateFormat = new SimpleDateFormat(WS_DATE_FORMAT);
12555     try {
12556       return simpleDateFormat.parse(dateString);
12557     } catch (ParseException e) {
12558       SimpleDateFormat simpleDateFormat2 = new SimpleDateFormat(WS_DATE_FORMAT2);
12559       try {
12560         return simpleDateFormat2.parse(dateString);
12561       } catch (ParseException e2) {
12562         throw new RuntimeException("Cannot convert '" + dateString
12563             + "' to a date based on format: " + WS_DATE_FORMAT, e);
12564       }
12565     }
12566   }
12567 
12568   /**
12569    * @param values
12570    * @return the max long in the list of args
12571    */
12572   public static Long getMaxLongValue(Long... values) {
12573     if (values == null || values.length == 0) {
12574       return null;
12575     }
12576 
12577     Long maxValue = null;
12578     for (int i = 0; i < values.length; i++) {
12579       if (values[i] != null) {
12580         if (maxValue == null || maxValue.compareTo(values[i]) < 0) {
12581           maxValue = new Long(values[i]);
12582         }
12583       }
12584     }
12585 
12586     return maxValue;
12587   }
12588 
12589   /**
12590    * @param values
12591    * @return the min long in the list of args
12592    */
12593   public static Long getMinLongValue(Long... values) {
12594     if (values == null || values.length == 0) {
12595       return null;
12596     }
12597 
12598     Long minValue = null;
12599     for (int i = 0; i < values.length; i++) {
12600       if (values[i] != null) {
12601         if (minValue == null || minValue.compareTo(values[i]) > 0) {
12602           minValue = new Long(values[i]);
12603         }
12604       }
12605     }
12606 
12607     return minValue;
12608   }
12609 
12610   /**
12611    * override map for properties in thread local to be used in a web server or the like, based on property file name
12612    * @param propertiesFileName
12613    * @return the override map
12614    */
12615   public static Map<String, String> propertiesThreadLocalOverrideMap(String propertiesFileName) {
12616     Map<String, Map<String, String>> overrideMap = propertiesThreadLocalOverrideMap.get();
12617     if (overrideMap == null) {
12618       overrideMap = new HashMap<String, Map<String, String>>();
12619       propertiesThreadLocalOverrideMap.set(overrideMap);
12620     }
12621     Map<String, String> propertiesOverrideMap = overrideMap.get(propertiesFileName);
12622     if (propertiesOverrideMap == null) {
12623       propertiesOverrideMap = new HashMap<String, String>();
12624       overrideMap.put(propertiesFileName, propertiesOverrideMap);
12625     }
12626     return propertiesOverrideMap;
12627   }
12628 
12629   /**
12630    * ^\s*\((.+)\)\s*([^\s]+)\s*$
12631    * start, optional space, open paren, stuff inside, close parent, optional space, not space, optional space
12632    */
12633   private static Pattern typeCastTypePattern = Pattern.compile("^\\s*\\((.+)\\)\\s*([^\\s]+)\\s*$");
12634 
12635   /**
12636    * process a string / string map and convert the values to a string/object map.
12637    * @param limitEnvVars if processing limits, pass in a map of limits.  The name is the
12638    * name of the variable, and the value is the value.  Note, you can typecast the
12639    * values by putting a valid type in parens in front of the param name.  e.g.
12640    * name: (integer)amount, value: 50         (will convert to long)
12641    * name: (decimal)amount, value: 50.3   (will convert to double)
12642    * name: (timestamp)amount, value: 2011/01/26 19:02:04   (will convert to date/timestamp)
12643    * @return the map of string to object
12644    */
12645   public static Map<String, Object> typeCastStringStringMap(Map<String, Object> limitEnvVars) {
12646 
12647     Map<String, Object> result = new LinkedHashMap<String, Object>();
12648 
12649     if (length(limitEnvVars) == 0) {
12650       return result;
12651     }
12652 
12653     Matcher matcher = null;
12654 
12655     for (String key : limitEnvVars.keySet()) {
12656 
12657       Object value = limitEnvVars.get(key);
12658       matcher = typeCastTypePattern.matcher(key);
12659       if (value instanceof String && matcher.matches()) {
12660         String type = StringUtils.trimToEmpty(matcher.group(1));
12661         Object valueOriginal = value;
12662         key = StringUtils.trimToEmpty(matcher.group(2));
12663         try {
12664           if (StringUtils.equalsIgnoreCase(type, "int") || StringUtils.equalsIgnoreCase(type, "integer")
12665               || StringUtils.equalsIgnoreCase(type, "long")) {
12666             value = longValue(value);
12667           } else if (StringUtils.equalsIgnoreCase(type, "double") || StringUtils.equalsIgnoreCase(type, "float")
12668               || StringUtils.equalsIgnoreCase(type, "decimal")) {
12669             value = doubleValue(value);
12670           } else if (StringUtils.equalsIgnoreCase(type, "date") || StringUtils.equalsIgnoreCase(type, "timestamp")) {
12671             value = toTimestamp(value);
12672           } else if (StringUtils.equalsIgnoreCase(type, "text") || StringUtils.equalsIgnoreCase(type, "string")) {
12673             //nothing, the value is a string
12674           } else if (StringUtils.equalsIgnoreCase(type, "boolean")) {
12675             value = booleanValue(value);
12676           } else if (StringUtils.equalsIgnoreCase(type, "null")) {
12677             value = null;
12678           } else if (StringUtils.equalsIgnoreCase(type, "empty") || StringUtils.equalsIgnoreCase(type, "emptyString")) {
12679             value = "";
12680           } else {
12681             throw new RuntimeException("Not expecting type: " + type + ", " + valueOriginal);
12682           }
12683         } catch (RuntimeException re) {
12684           throw new RuntimeException("Cannot convert value to " + key + ", " + type + ", " + valueOriginal, re);
12685         }
12686       }
12687 
12688       result.put(key, value);
12689 
12690     }
12691     return result;
12692   }
12693 
12694   /**
12695    * see if an ip address is on a network
12696    *
12697    * @param ipString
12698    *          is the ip address to check
12699    * @param networkIpString
12700    *          is the ip address of the network
12701    * @param mask
12702    *          is the length of the mask (0-32)
12703    * @return boolean
12704    */
12705   public static boolean ipOnNetwork(String ipString, String networkIpString, int mask) {
12706 
12707     //this allows all
12708     if (mask == 0) {
12709       return true;
12710     }
12711     
12712     if (StringUtils.equals(networkIpString, "127.0.0.1") && StringUtils.equals(ipString, "0:0:0:0:0:0:0:1")) {
12713       return true;
12714     }
12715 
12716     // might work for ipv6?
12717     if (StringUtils.equals(networkIpString, ipString)) {
12718       return true;
12719     }
12720 
12721     if (ipString.contains(":") || networkIpString.contains(":")) {
12722       return false;
12723     }
12724     
12725     int ip = ipInt(ipString);
12726     int networkIp = ipInt(networkIpString);
12727 
12728     ip = ipReadyForAnd(ip, mask);
12729     networkIp = ipReadyForAnd(networkIp, mask);
12730 
12731     return ip == networkIp;
12732   }
12733 
12734   /**
12735    * matches 123.234.12.34
12736    */
12737   private static Pattern ipv4regex = Pattern.compile("^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$");
12738   
12739   /**
12740    * see if an ip address is on a network
12741    *
12742    * @param ipString
12743    *          is the ip address to check
12744    * @param networkIpStrings
12745    *          are the ip addresses of the networks, e.g. 1.2.3.4/12, 2.3.4.5/24, 1.2.3.4
12746    * @return boolean
12747    */
12748   public static boolean ipOnNetworks(String ipString, String networkIpStrings) {
12749 
12750     String[] networkIpStringsArray = splitTrim(networkIpStrings, ",");
12751 
12752     //check each one
12753     for (String networkIpString : networkIpStringsArray) {
12754 
12755       if (ipv4regex.matcher(networkIpString).matches()) {
12756         networkIpString = networkIpString + "/32";
12757       }
12758       
12759       if (!contains(networkIpString, "/")) {
12760         throw new RuntimeException("String must contain slash and CIDR network bits, e.g. 1.2.3.4/14");
12761       }
12762       //get network part:
12763       String network = prefixOrSuffix(networkIpString, "/", true);
12764       network = trim(network);
12765 
12766       String mask = prefixOrSuffix(networkIpString, "/", false);
12767       mask = trim(mask);
12768       int maskInt = -1;
12769 
12770       maskInt = Integer.parseInt(mask);
12771 
12772       //if on the network, then all good
12773       if (ipOnNetwork(ipString, network, maskInt)) {
12774         return true;
12775       }
12776 
12777 
12778     }
12779     return false;
12780   }
12781 
12782   /**
12783    * get the ip address after putting 1's where the subnet mask is not
12784    * @param ip int
12785    * @param maskLength int
12786    * @return int
12787    */
12788   public static int ipReadyForAnd(int ip, int maskLength) {
12789     int mask = -1 + (int) Math.pow(2, 32 - maskLength);
12790 
12791     return ip | mask;
12792   }
12793 
12794   /**
12795    * get the ip addres integer from a string ip address
12796    * @param ip String
12797    * @return int
12798    */
12799   public static int ipInt(String ip) {
12800     int block1;
12801     int block2;
12802     int block3;
12803     int block4;
12804 
12805     try {
12806       int periodIndex = ip.indexOf('.');
12807       String blockString = ip.substring(0, periodIndex);
12808       block1 = Integer.parseInt(blockString);
12809 
12810       //split it up for 2^24 since it does the math wrong if you dont
12811       int mathPow = (int) Math.pow(2, 24);
12812       block1 *= mathPow;
12813 
12814       int oldPeriodIndex = periodIndex;
12815 
12816       periodIndex = ip.indexOf('.', periodIndex + 1);
12817       blockString = ip.substring(oldPeriodIndex + 1, periodIndex);
12818       block2 = Integer.parseInt(blockString);
12819       block2 *= Math.pow(2, 16);
12820       oldPeriodIndex = periodIndex;
12821 
12822       periodIndex = ip.indexOf('.', periodIndex + 1);
12823       blockString = ip.substring(oldPeriodIndex + 1, periodIndex);
12824       block3 = Integer.parseInt(blockString);
12825       block3 *= Math.pow(2, 8);
12826 
12827       blockString = ip.substring(periodIndex + 1, ip.length());
12828       block4 = Integer.parseInt(blockString);
12829     } catch (NumberFormatException nfe) {
12830       throw new RuntimeException("Could not parse the ipaddress: " + ip);
12831     }
12832 
12833     return block1 + block2 + block3 + block4;
12834   }
12835 
12836   /**
12837    * get the set of methods
12838    * @param theClass
12839    * @param methodName
12840    * @param superclassToStopAt
12841    * @param includeSuperclassToStopAt
12842    * @param includeStaticMethods
12843    * @param markerAnnotation
12844    * @param includeAnnotation
12845    * @param methodSet
12846    */
12847   public static void methodsByNameHelper(Class<?> theClass, String methodName, Class<?> superclassToStopAt,
12848       boolean includeSuperclassToStopAt,
12849       boolean includeStaticMethods, Class<? extends Annotation> markerAnnotation,
12850       boolean includeAnnotation, Set<Method> methodSet) {
12851     theClass = unenhanceClass(theClass);
12852     Method[] methods = theClass.getDeclaredMethods();
12853     if (length(methods) != 0) {
12854       for (Method method : methods) {
12855         // if not static, then continue
12856         if (!includeStaticMethods
12857             && Modifier.isStatic(method.getModifiers())) {
12858           continue;
12859         }
12860         // if checking for annotation
12861         if (markerAnnotation != null
12862             && (includeAnnotation != method
12863                 .isAnnotationPresent(markerAnnotation))) {
12864           continue;
12865         }
12866         if (StringUtils.equals(methodName, method.getName())) {
12867           // go for it
12868           methodSet.add(method);
12869         }
12870       }
12871     }
12872     // see if done recursing (if superclassToStopAt is null, then stop at
12873     // Object
12874     if (theClass.equals(superclassToStopAt)
12875         || theClass.equals(Object.class)) {
12876       return;
12877     }
12878     Class superclass = theClass.getSuperclass();
12879     if (!includeSuperclassToStopAt && superclass.equals(superclassToStopAt)) {
12880       return;
12881     }
12882     // recurse
12883     methodsByNameHelper(superclass, methodName, superclassToStopAt,
12884         includeSuperclassToStopAt, includeStaticMethods,
12885         markerAnnotation, includeAnnotation, methodSet);
12886 
12887   }
12888 
12889   /**
12890    * call method with more params.  e.g. if you are only passing in the first 3 params, of a 6 param method...
12891    * @param instance
12892    * @param theType of object to call method on
12893    * @param methodName
12894    * @param params
12895    * @return the result of the method
12896    */
12897   public static Object callMethodWithMoreParams(Object instance, Class<?> theType, String methodName, Object[] params) {
12898     Method method = methodByName(theType, methodName, Object.class, false, instance == null ? true : false, true);
12899 
12900     int paramsSize = length(params);
12901 
12902     Class<?>[] methodParamTypes = method.getParameterTypes();
12903 
12904     int methodParamsSize = length(methodParamTypes);
12905     if (methodParamsSize < paramsSize) {
12906       throw new RuntimeException("Problem marshaling between Grouper versions... why is method params "
12907           + methodParamsSize + " less than args? " + paramsSize + " for method: " + method);
12908     }
12909 
12910     //lets see if the params match up, as many as there are
12911     for (int i=0;i<paramsSize;i++) {
12912       Class<?> methodParamClass = methodParamTypes[i];
12913 
12914       //make sure assignable
12915       if (methodParamTypes[i].isPrimitive() && params[i] == null) {
12916         throw new RuntimeException("Trying to call method: " + methodName + " on class: " + (instance == null ? null : instance.getClass())
12917             + " and arg index: " + i + " is null: but should be a primitive: " + methodParamTypes[i].getName());
12918       }
12919 
12920       if (params[i] != null && !(methodParamTypes[i].isAssignableFrom(params[i].getClass()))) {
12921         throw new RuntimeException("Trying to call method: " + methodName + " on class: " + (instance == null ? null : instance.getClass())
12922             + " and arg index: " + i + " is of type: " + methodParamClass + ", but trying to pass: " + params[i].getClass());
12923       }
12924 
12925     }
12926 
12927     Object[] methodArgs = new Object[methodParamsSize];
12928 
12929     //copy the args, the rest are null
12930     if (paramsSize > 0) {
12931       System.arraycopy(params, 0, methodArgs, 0, params.length);
12932 
12933     }
12934 
12935     //we are all good, just pass null for the extra methods
12936     try {
12937       return method.invoke(instance, methodArgs);
12938     } catch (Exception e) {
12939       throw new RuntimeException("Trying to call method: " + methodName + " on class: " + (instance == null ? null : instance.getClass()), e);
12940     }
12941   }
12942 
12943   /**
12944    * convert an object from one version to another
12945    * @param input input object
12946    * @param newPackage is the package where the other version of things are
12947    * @return the object in the new version or null if input null or new version not found
12948    */
12949   public static Object changeToVersion(Object input, String newPackage) {
12950     return changeToVersionHelper(input, newPackage, 100);
12951   }
12952 
12953   /**
12954    * convert an object from one version to another
12955    * @param input input object
12956    * @param newPackage is the package where the other version of things are
12957    * @param timeToLive avoid circular references
12958    * @return the object in the new version or null if input null or new version not found
12959    */
12960   public static Object changeToVersionHelper(Object input, String newPackage, int timeToLive) {
12961 
12962 
12963     if (input == null) {
12964       return null;
12965     }
12966 
12967     //if we are a string, or stringbuilder or whatever, just return it
12968     if (!input.getClass().isArray() && StringUtils.equals("java.lang", input.getClass().getPackage().getName())) {
12969       //lets clone, not sure why...
12970       if (input instanceof StringBuilder) {
12971         return new StringBuilder((StringBuilder)input);
12972       }
12973       return input;
12974     }
12975 
12976     //lets get the input class
12977     Class inputClass = input.getClass();
12978 
12979     int interestingLogFields = 0;
12980 
12981     //if we are an array of strings, clone and return it
12982     int inputArrayLength = inputClass.isArray() ? length(input) : -1;
12983     if (inputClass.isArray() && String.class.equals(inputClass.getComponentType())) {
12984       //lets clone
12985       String[] result = new String[inputArrayLength];
12986       System.arraycopy(input, 0, result, 0, result.length);
12987       return result;
12988     }
12989 
12990     //why would be have a java lang array at this point?
12991     if (input.getClass().getName().startsWith("java.lang") && input.getClass().isArray()) {
12992       throw new RuntimeException("Not expecting class: " + input.getClass());
12993     }
12994 
12995     StringBuilder logMessage = LOG.isDebugEnabled() ? new StringBuilder() : null;
12996 
12997     if (logMessage != null) {
12998       logMessage.append("class: ").append(inputClass.getSimpleName()).append(", ");
12999     }
13000 
13001     if (timeToLive-- < 0) {
13002       throw new RuntimeException("Circular reference!");
13003     }
13004 
13005     //new class
13006     try {
13007 
13008       Object result = null;
13009 
13010       Class<?> outputClass = null;
13011       String outputClassName = newPackage + "." + (inputClass.isArray() ?
13012           inputClass.getComponentType().getSimpleName() : inputClass.getSimpleName());
13013       try {
13014         outputClass = forName(outputClassName);
13015       } catch (RuntimeException re) {
13016         if (re.getCause() instanceof ClassNotFoundException) {
13017           if (logMessage != null) {
13018             logMessage.append("output classNotFound: ").append(outputClassName);
13019             LOG.debug(logMessage.toString());
13020           }
13021           return null;
13022         }
13023         //let this be handled below
13024         throw re;
13025       }
13026 
13027 
13028 
13029       //if we are an array of objects, do that
13030       if (inputClass.isArray()) {
13031         Object array = Array.newInstance(outputClass, inputArrayLength);
13032         for (int i=0;i<inputArrayLength;i++) {
13033 
13034           Object inputElement = Array.get(input, i);
13035           Object outputElement = changeToVersionHelper(inputElement, newPackage, timeToLive);
13036           Array.set(array, i, outputElement);
13037 
13038         }
13039         return array;
13040       }
13041 
13042 
13043       //get instance
13044       result = newInstance(outputClass);
13045 
13046       //get all fields in the input
13047       Set<Field> inputFields = fields(inputClass, Object.class, null, false, false, false, null, false);
13048       Set<Field> outputFields = fields(outputClass, Object.class, null, false, false, false, null, false);
13049 
13050       Map<String, Field> inputFieldMap = new HashMap<String, Field>();
13051 
13052       for (Field field : nonNull(inputFields)) {
13053         inputFieldMap.put(field.getName(), field);
13054       }
13055 
13056       //see which ones match
13057       for (Field outputField : nonNull(outputFields)) {
13058 
13059         Field inputField = inputFieldMap.get(outputField.getName());
13060 
13061         if (inputField == null) {
13062           if (logMessage != null) {
13063             interestingLogFields++;
13064             logMessage.append("field not found input: ").append(outputField.getName()).append(", ");
13065           }
13066           continue;
13067         }
13068         //take it out of the map so we know which ones are left
13069         inputFieldMap.remove(inputField.getName());
13070 
13071         Object inputFieldObject = fieldValue(inputField, input);
13072 
13073         //lets convert that field
13074         Object outputFieldObject = changeToVersionHelper(inputFieldObject, newPackage, timeToLive);
13075 
13076         //this is ok
13077         if (outputFieldObject == null) {
13078           continue;
13079         }
13080 
13081         try {
13082           assignField(outputField, result, outputFieldObject, true, false);
13083         } catch (RuntimeException re) {
13084           if (logMessage != null) {
13085             logMessage.append("problem with field: ").append(inputField.getName()).append(", ").append(ExceptionUtils.getFullStackTrace(re));
13086             interestingLogFields++;
13087           }
13088         }
13089       }
13090       
13091       if (result instanceof ChangeToVersionCustomizable) {
13092         ((ChangeToVersionCustomizable)result).customizeChangeToVersion(input);
13093       }
13094       if (input instanceof ChangeToVersionCustomizable) {
13095         ((ChangeToVersionCustomizable)input).customizeChangeFromVersion(result);
13096       }
13097       
13098       if (logMessage != null) {
13099         for (String inputFieldName : nonNull(inputFieldMap.keySet())) {
13100           logMessage.append("field not found output: ").append(inputFieldName).append(", ");
13101           interestingLogFields++;
13102         }
13103 
13104         if (interestingLogFields > 0) {
13105           LOG.debug(logMessage.toString());
13106         }
13107       }
13108 
13109       return result;
13110     } catch (RuntimeException re) {
13111       if (logMessage != null) {
13112         logMessage.append("Problem with class: ").append(re.getClass()).append(", ").append(re.getMessage());
13113         LOG.debug(logMessage.toString(), re);
13114       }
13115       throw re;
13116     }
13117 
13118   }
13119 
13120   /**
13121    *
13122    */
13123   public static final String JAVA_IO_TMPDIR = "java.io.tmpdir";
13124 
13125   /** original tmp dir */
13126   private static final String ORIGINAL_TMP_DIR = System.getProperty(JAVA_IO_TMPDIR);
13127 
13128   /** log it once */
13129   private static boolean loggedTempDir = false;
13130 
13131   /**
13132    * return the temp dir, either what is in the java env var, or something in the grouper conf
13133    * @return the temp dir
13134    */
13135   public static String tmpDir() {
13136     return tmpDir(false);
13137   }
13138 
13139   /**
13140    * return the temp dir, either what is in the java env var, or something in the grouper conf
13141    * @param appendSlashIfNotThere add a slash to end if not there already
13142    * @return the temp dir
13143    */
13144   public static String tmpDir(boolean appendSlashIfNotThere) {
13145 
13146     String tmpDir = null;
13147 
13148     tmpDir = GrouperConfig.retrieveConfig().propertyValueString("grouper.tmp.dir");
13149     if (isBlank(tmpDir)) {
13150       tmpDir = ORIGINAL_TMP_DIR;
13151       if (isBlank(tmpDir)) {
13152         //logger might not be set, lets SOP this too...
13153         System.out.println("Error: Cant find tmpDir.  You should set grouper.tmp.dir in the grouper.properties!");
13154         LOG.fatal("Error: Cant find tmpDir.  You should set grouper.tmp.dir in the grouper.properties!");
13155       }
13156     }
13157     if (!isBlank(tmpDir)) {
13158       if (!loggedTempDir) {
13159         loggedTempDir = true;
13160         LOG.info("Tmp dir is set to: '" + tmpDir + "'");
13161       }
13162       if (appendSlashIfNotThere) {
13163         tmpDir = StringUtils.trimToEmpty(tmpDir);
13164         if (!tmpDir.endsWith("\\") && !tmpDir.endsWith("/")) {
13165           tmpDir += File.separator;
13166         }
13167       }
13168     }
13169     return tmpDir;
13170   }
13171 
13172   /**
13173    * null safe convert collection to list
13174    * @param <T>
13175    * @param collection
13176    * @return the list
13177    */
13178   public static <T> List<T> listFromCollection(Collection<T> collection) {
13179     return collection == null ? null : new ArrayList<T>(collection);
13180   }
13181 
13182   /**
13183    * threadpool
13184    */
13185   private static ExecutorService executorService = Executors.newCachedThreadPool(new DaemonThreadFactory());
13186 
13187   /**
13188    * might want to use GrouperCallable with this
13189    * @return executor service
13190    */
13191   public static ExecutorService retrieveExecutorService() {
13192     return executorService;
13193   }
13194 
13195   /**
13196    * you should probably use the GrouperCallable method, not this one
13197    * @param executorService
13198    * @param callable
13199    * @return the future
13200    */
13201   public static <T> GrouperFuture<T> executorServiceSubmit(ExecutorService executorService, Callable<T> callable) {
13202     Future future = executorService.submit(callable);
13203     GrouperFuturerouperFuture.html#GrouperFuture">GrouperFuture grouperFuture = new GrouperFuture(future, callable);
13204     return grouperFuture;
13205   }
13206   
13207   /**
13208    * 
13209    * @param executorService
13210    * @param callable
13211    * @param willRetry 
13212    * @return the future
13213    */
13214   public static GrouperFuture executorServiceSubmit(ExecutorService executorService, GrouperCallable callable, boolean willRetry) {
13215     callable.setWillRetry(willRetry);
13216     Future future = executorService.submit(callable);
13217     GrouperFuturerouperFuture.html#GrouperFuture">GrouperFuture grouperFuture = new GrouperFuture(future, callable);
13218     return grouperFuture;
13219   }
13220   
13221   /**
13222    * concat two strings
13223    * @param a
13224    * @param b
13225    * @return the concatted string
13226    */
13227   public static String concat(String a, String b) {
13228     return a+b;
13229   }
13230   
13231   /**
13232    * concat strings
13233    * @param a
13234    * @param b
13235    * @param c
13236    * @return the concatted string
13237    */
13238   public static String concat(String a, String b, String c) {
13239     return a+b+c;
13240   }
13241   
13242   /**
13243    * concat strings
13244    * @param a
13245    * @param b
13246    * @param c
13247    * @param d
13248    * @return the concatted string
13249    */
13250   public static String concat(String a, String b, String c, String d) {
13251     return a+b+c+d;
13252   }
13253   
13254   /**
13255    * concat strings
13256    * @param a
13257    * @param b
13258    * @param c
13259    * @param d
13260    * @return the concatted string
13261    */
13262   public static String concat(String a, String b, String c, String d, String e) {
13263     return a+b+c+d+e;
13264   }
13265 
13266   /**
13267    * get a couple of lines of stack
13268    * @return the couple of lines of stack
13269    */
13270   public static String stack() {
13271     Exception exception = new Exception();
13272     //get the stack
13273     String stack = getFullStackTrace(exception);
13274     //split into an array of strings (on newline)
13275     String[] stackLines = splitTrim(stack, "\n");
13276     
13277     StringBuffer result = new StringBuffer();
13278     
13279     for (int i=0;i<stackLines.length;i++) {
13280       String current = stackLines[i];
13281       if (current.startsWith("at edu.internet2") && !current.startsWith("at edu.internet2.middleware.grouper.util.GrouperUtil.stack(")) {
13282         if (result.length() > 0) {
13283           result.append(", ");
13284         }
13285         result.append(extractFileLine(current));
13286       }
13287     }
13288     return result.toString();
13289   }
13290   
13291   
13292   /**
13293    * pattern to get the file, method, and line number:
13294    * ^.*    beginning
13295    * \\.    dot
13296    * (.+) method
13297    * \\(    open paren
13298    * (.+)   file name
13299    * :      colon
13300    * (\\d+) line number
13301    * \\)    close paren
13302    * .*$      end
13303    */
13304   private static Pattern extractFileLinePattern = Pattern.compile("^.*\\.(.+)\\((.+):(\\d+)\\).*$"); 
13305   
13306   /**
13307    * get the good part out of the full line (or just return it if the parsing doesnt work)
13308    * @param fullLine
13309    * @return the file, method, line
13310    */
13311   private static String extractFileLine(String fullLine) {
13312     Matcher matchResult = extractFileLinePattern.matcher(fullLine);
13313 
13314     if (matchResult.find()) {
13315       String method = matchResult.group(1);
13316       String file = matchResult.group(2);
13317       if (file.endsWith(".java")) {
13318         file = file.substring(0, file.length() - ".java".length());
13319       }
13320       String line = matchResult.group(3);
13321       return file + "." + method + "() line " + line;
13322     }
13323     return fullLine;
13324   }
13325 
13326   /**
13327    * based on some ids, sort a set of grouper objects by that
13328    * @param <T>
13329    * @param ids
13330    * @param grouperObjects
13331    * @return the set of grouper objects
13332    */
13333   public static <T extends GrouperId> Set<T> sortByIds(Collection<String> ids, Collection<T> grouperObjects) {
13334 
13335     //if null, do nothing
13336     if (grouperObjects == null) {
13337       return null;
13338     }
13339     Set<T> result = new LinkedHashSet<T>();
13340     
13341     //if empty, we done
13342     if (grouperObjects.size() > 0) {
13343       
13344       //make a map of objects
13345       Map<String, T> lookupMap = new HashMap<String, T>();
13346       
13347       for (T grouperObject : grouperObjects) {
13348         lookupMap.put(grouperObject.getId(), grouperObject);
13349       }
13350       
13351       //loop through the ids, and put the objects in the result
13352       //keep track of ids we have used
13353       Set<String> idsWeHaveUsed = new HashSet<String>();
13354       
13355       for (String id : ids) {
13356         T grouperObject = lookupMap.get(id);
13357         if (grouperObject != null) {
13358           result.add(grouperObject);
13359 
13360           idsWeHaveUsed.add(id);
13361           
13362         }
13363       }
13364 
13365       //this doesnt really make sense, but if there are any objects we havent used, put them in there
13366       for (T grouperObject : grouperObjects) {
13367         if (!idsWeHaveUsed.contains(grouperObject.getId())) {
13368           result.add(grouperObject);
13369         }
13370       }
13371       
13372     }
13373     
13374     return result;
13375   }
13376   
13377   /**
13378    * @return grouper home
13379    */
13380   public static String getGrouperHome() {
13381     return grouperHome;
13382   }
13383   
13384   /**
13385    * split a file on whitespace
13386    * @param fileContents
13387    * @return the list of file lines
13388    */
13389   public static List<String> splitFileLines(String fileContents) {
13390     fileContents = StringUtils.replace(fileContents, "\r\n", "\n");
13391     fileContents = StringUtils.replace(fileContents, "\r", "\n");
13392     // keep whitespace in there...
13393     String[] resultArray = StringUtils.splitPreserveAllTokens(fileContents, '\n');
13394     List<String> result = GrouperUtil.toList(resultArray);
13395     return result;
13396   }
13397   
13398   /** array for converting HTML to string */
13399   public static final String[] HTML_REPLACE = new String[]{"&amp;","&lt;","&gt;","&#39;","&quot;"};
13400 
13401   /** array for converting HTML to string */
13402   public static final String[] HTML_REPLACE_NO_SINGLE = new String[]{"&amp;","&lt;","&gt;","&quot;"};
13403 
13404   /** array for converting HTML to string */
13405   private static final String[] HTML_SEARCH = new String[]{"&","<",">","'","\""};
13406 
13407   /** array for converting HTML to string */
13408   public static final String[] HTML_SEARCH_NO_SINGLE = new String[]{"&","<",">","\""};
13409 
13410   /**
13411    * Convert an XML string to HTML to display on the screen
13412    * 
13413    * @param input
13414    *          is the XML to convert
13415    * @param isEscape true to escape chars, false to unescape
13416    * 
13417    * @return the HTML converted string
13418    */
13419   public static String escapeHtml(String input, boolean isEscape) {
13420     return escapeHtml(input, isEscape, true);
13421   }
13422 
13423   /**
13424    * Convert an XML string to HTML to display on the screen
13425    * 
13426    * @param input
13427    *          is the XML to convert
13428    * @param isEscape true to escape chars, false to unescape
13429    * @param escapeSingleQuotes true to escape single quotes too
13430    * 
13431    * @return the HTML converted string
13432    */
13433   public static String escapeHtml(String input, boolean isEscape, boolean escapeSingleQuotes) {
13434     if (escapeSingleQuotes) {
13435       if (isEscape) {
13436         return GrouperUtil.replace(input, HTML_SEARCH, HTML_REPLACE);
13437       }
13438       return GrouperUtil.replace(input, HTML_REPLACE, HTML_SEARCH);
13439     }
13440     if (isEscape) {
13441       return GrouperUtil.replace(input, HTML_SEARCH_NO_SINGLE, HTML_REPLACE_NO_SINGLE);
13442     }
13443     return GrouperUtil.replace(input, HTML_REPLACE_NO_SINGLE, HTML_SEARCH_NO_SINGLE);
13444     
13445   }
13446 
13447   /**
13448    * list files recursively from parent, dont include 
13449    * @param parent
13450    * @return set of files wont return null
13451    */
13452   public static List<File> fileListRecursive(File parent) {
13453     List<File> results = new ArrayList<File>();
13454     fileListRecursiveHelper(parent, results);
13455     return results;
13456   }
13457   
13458   /**
13459    * helper to add child files to a parent (
13460    * @param parent
13461    * @param fileList
13462    */
13463   private static void fileListRecursiveHelper(File parent, List<File> fileList) {
13464     if (parent == null || !parent.exists() || !parent.isDirectory()) {
13465       return;
13466     }
13467     List<File> subFiles = nonNull(toList(parent.listFiles()));
13468     for (File subFile : subFiles) {
13469       if (subFile.isFile()) {
13470         fileList.add(subFile);
13471       }
13472       if (subFile.isDirectory()) {
13473         fileListRecursiveHelper(subFile, fileList);
13474       }
13475     }
13476   }
13477   
13478   /**
13479    * list dirs recursively from parent, dont include 
13480    * @param parent
13481    * @return set of files wont return null
13482    */
13483   public static List<File> fileListRecursiveDirs(File parent) {
13484     List<File> results = new ArrayList<File>();
13485     fileListRecursiveDirsHelper(parent, results);
13486     return results;
13487   }
13488   
13489   /**
13490    * helper to add child dirs to a parent (
13491    * @param parent
13492    * @param fileList
13493    */
13494   private static void fileListRecursiveDirsHelper(File parent, List<File> fileList) {
13495     if (parent == null || !parent.exists() || !parent.isDirectory()) {
13496       return;
13497     }
13498     List<File> subFiles = nonNull(toList(parent.listFiles()));
13499     for (File subFile : subFiles) {
13500       if (subFile.isDirectory()) {
13501         fileList.add(subFile);
13502         fileListRecursiveDirsHelper(subFile, fileList);
13503       }
13504     }
13505   }
13506   
13507   /**
13508    * get the relative paths of descendant files
13509    * @param parentDir
13510    * @return the relative paths of files underneath, dont start with slash
13511    */
13512   public static Set<String> fileDescendantDirsRelativePaths(File parentDir) {
13513     Set<String> result = new LinkedHashSet<String>();
13514     List<File> descendants = fileListRecursiveDirs(parentDir);
13515     for (File file : nonNull(descendants)) {
13516       String descendantPath = file.getAbsolutePath();
13517       String parentPath = parentDir.getAbsolutePath();
13518       if (!descendantPath.startsWith(parentPath)) {
13519         throw new RuntimeException("Why doesnt descendantPath '" + descendantPath + "' start with parent path '" + parentPath + "'?");
13520       }
13521       descendantPath = descendantPath.substring(parentPath.length());
13522       if (descendantPath.startsWith("/") || descendantPath.startsWith("\\")) {
13523         descendantPath = descendantPath.substring(1);
13524       }
13525       result.add(descendantPath);
13526     }
13527     return result;
13528   }
13529 
13530   /**
13531    * get the relative paths of descendant files
13532    * @param parentDir
13533    * @return the relative paths of files underneath, dont start with slash
13534    */
13535   public static Set<String> fileDescendantRelativePaths(File parentDir) {
13536     Set<String> result = new LinkedHashSet<String>();
13537     List<File> descendants = fileListRecursive(parentDir);
13538     for (File file : nonNull(descendants)) {
13539       String descendantPath = file.getAbsolutePath();
13540       String parentPath = parentDir.getAbsolutePath();
13541       if (!descendantPath.startsWith(parentPath)) {
13542         throw new RuntimeException("Why doesnt descendantPath '" + descendantPath + "' start with parent path '" + parentPath + "'?");
13543       }
13544       descendantPath = descendantPath.substring(parentPath.length());
13545       if (descendantPath.startsWith("/") || descendantPath.startsWith("\\")) {
13546         descendantPath = descendantPath.substring(1);
13547       }
13548       result.add(descendantPath);
13549     }
13550     return result;
13551   }
13552 
13553   /**
13554    * in a map get a value if there and increment or set a value
13555    * @param map
13556    * @param key
13557    * @param numberToAdd long
13558    */
13559   public static void mapAddValue(Map<String, Object> map, String key, long numberToAdd) {
13560     if (map == null) {
13561       return;
13562     }
13563     
13564     Object currentValue = map.get(key);
13565     
13566     if (currentValue == null) {
13567       
13568       currentValue = 0L;
13569     }
13570 
13571     long newValue = GrouperUtil.longValue(currentValue) + numberToAdd;
13572     
13573     map.put(key, newValue);
13574     
13575   }
13576 
13577   /**
13578    * in a map get a value if there and increment or set a value
13579    * @param map
13580    * @param key
13581    * @param numberToAdd int
13582    */
13583   public static void mapAddValue(Map<String, Object> map, String key, int numberToAdd) {
13584     if (map == null) {
13585       return;
13586     }
13587     
13588     Object currentValue = map.get(key);
13589     
13590     if (currentValue == null) {
13591       
13592       currentValue = 0;
13593     }
13594 
13595     int newValue = GrouperUtil.intValue(currentValue) + numberToAdd;
13596     
13597     map.put(key, newValue);
13598 
13599   }
13600 
13601   /**
13602    * substitute an EL for objects
13603    * @param stringToParse
13604    * @param variableMap
13605    * @param allowStaticClasses if true allow static classes not registered with context
13606    * @param silent if silent mode, swallow exceptions (warn), and dont warn when variable not found
13607    * @param lenient false if undefined variables should throw an exception.  if lenient is true (default)
13608    * then undefined variables are null
13609    * @return the object
13610    */
13611   @SuppressWarnings("unchecked")
13612   public static String substituteExpressionLanguageTemplate(String script,
13613       Map<String, Object> variableMap, boolean allowStaticClasses, boolean silent, boolean lenient) {
13614     variableMap = nonNull(variableMap);
13615     substituteExpressionInit3();
13616     
13617     if (isBlank(script)) {
13618       return null;
13619     }
13620 
13621     // TODO replace when we upgrade jexl templates to allow multiple newline
13622     //  Caused by: java.lang.StringIndexOutOfBoundsException: String index out of range: 0
13623     //  at java.lang.String.charAt(String.java:658)
13624     //  at org.apache.commons.jexl2.UnifiedJEXL.startsWith(UnifiedJEXL.java:1350)
13625     //  at org.apache.commons.jexl2.UnifiedJEXL.readTemplate(UnifiedJEXL.java:1413)
13626     //  at org.apache.commons.jexl2.UnifiedJEXL$Template.<init>(UnifiedJEXL.java:1083)
13627     //  at org.apache.commons.jexl2.UnifiedJEXL.createTemplate(UnifiedJEXL.java:1462)
13628     //  at edu.internet2.middleware.grouper.util.GrouperUtil.substituteExpressionLanguageTemplate(GrouperUtil.java:13059)
13629 //    while (script.contains("\n\n")) {
13630 //      script = replace(script, "\n\n", "\n");
13631 //    }
13632 
13633     String overallResult = null;
13634     Exception exception = null;
13635     try {
13636       org.apache.commons.jexl3.JexlContext jc = allowStaticClasses ? new GrouperMapContext3() : new org.apache.commons.jexl3.MapContext();
13637   
13638       int index = 0;
13639   
13640       for (String key: variableMap.keySet()) {
13641         jc.set(key, variableMap.get(key));
13642       }
13643   
13644       //allow utility methods
13645       jc.set("grouperUtil", new GrouperUtilElSafe());
13646       //if you add another one here, add it in the logs below
13647 
13648       script = script.trim();
13649 
13650       org.apache.commons.jexl3.JexlEngine jexl = jexlEngines3.get(new MultiKey(silent, lenient));
13651       JxltEngine jxlt = jexl.createJxltEngine();
13652       Template theTemplate = jxlt.createTemplate(script);
13653       
13654       Writer writer = new StringWriter();
13655   
13656       String result = null;
13657 
13658       //this is the result of the evaluation
13659       try {
13660         theTemplate.evaluate(jc, writer);
13661         
13662         result = writer.toString();
13663       } catch (JexlException je) {
13664         //exception-scrape to see if missing variable
13665         if (!lenient && StringUtils.trimToEmpty(je.getMessage()).contains("undefined variable")) {
13666           //clean up the message a little bit
13667           // e.g. edu.internet2.middleware.grouper.util.GrouperUtil.substituteExpressionLanguage@8846![0,6]: 'amount < 50000 && amount2 < 23;' undefined variable amount
13668           String message = je.getMessage();
13669           //Pattern exceptionPattern = Pattern.compile("^" + GrouperUtil.class.getName() + "\\.substituteExpressionLanguage.*?]: '(.*)");
13670           Pattern exceptionPattern = Pattern.compile("^.*undefined variable (.*)");
13671           Matcher exceptionMatcher = exceptionPattern.matcher(message);
13672           if (exceptionMatcher.matches()) {
13673             //message = "'" + exceptionMatcher.group(1);
13674             message = "variable '" + exceptionMatcher.group(1) + "' is not defined in script: '" + script + "'";
13675           }
13676           throw new ExpressionLanguageMissingVariableException(message, je);
13677         }
13678         throw je;
13679       }
13680       // for some reason this adds newlines...  trim them
13681       return trim(result);
13682     } catch (HookVeto hv) {
13683       exception = hv;
13684       throw hv;
13685     } catch (Exception e) {
13686       exception = e;
13687       if (e instanceof ExpressionLanguageMissingVariableException) {
13688         throw (ExpressionLanguageMissingVariableException)e;
13689       }
13690       throw new RuntimeException("Error substituting string: '" + script + "', substituteVarNames: " + setToString(variableMap.keySet()), e);
13691     } finally {
13692       if (LOG.isDebugEnabled()) {
13693         Set<String> keysSet = new LinkedHashSet<String>(nonNull(variableMap).keySet());
13694         keysSet.add("grouperUtil");
13695         StringBuilder logMessage = new StringBuilder();
13696         logMessage.append("Subsituting EL: '").append(script).append("', and with env vars: ");
13697         String[] keys = keysSet.toArray(new String[]{});
13698         for (int i=0;i<keys.length;i++) {
13699           logMessage.append(keys[i]);
13700           if (i != keys.length-1) {
13701             logMessage.append(", ");
13702           }
13703         }
13704         logMessage.append(" with result: '" + overallResult + "'");
13705         if (exception != null) {
13706           if (exception instanceof HookVeto) {
13707             logMessage.append(", it was vetoed: " + exception);
13708           } else {
13709             logMessage.append(", and exception: " + exception + ", " + ExceptionUtils.getFullStackTrace(exception));
13710           }
13711         }
13712         LOG.debug(logMessage.toString());
13713       }
13714     }
13715   }
13716 
13717   /**
13718    * create one set of jexlEngine instances (one per type of setting) so we can cache expressions
13719    */
13720   private final static Map<MultiKey, org.apache.commons.jexl3.JexlEngine> jexlEngines3 = new HashMap<MultiKey, org.apache.commons.jexl3.JexlEngine>();
13721   
13722   /**
13723    * if the jexl engine instances are all initialized completely
13724    */
13725   private static boolean jexlEnginesInitialized3 = false;
13726 
13727   private static void substituteExpressionInit() {
13728     if (!jexlEnginesInitialized) {
13729       synchronized (GrouperUtil.class) {
13730         if (!jexlEnginesInitialized) {
13731           
13732           int cacheSize = GrouperConfig.retrieveConfig().propertyValueInt("jexl.cacheSize");
13733           for (JexlEngine jexlEngine : jexlEngines.values()) {
13734             jexlEngine.setCache(cacheSize);
13735           }
13736           
13737           jexlEnginesInitialized = true;
13738         }
13739       }
13740     }
13741   }
13742   
13743   private static void substituteExpressionInit3() {
13744     if (!jexlEnginesInitialized3) {
13745       synchronized (GrouperUtil.class) {
13746         if (!jexlEnginesInitialized3) {
13747           
13748           int cacheSize = GrouperConfig.retrieveConfig().propertyValueInt("jexl.cacheSize");
13749           { 
13750             Boolean silent = true;
13751             Boolean lenient = true;
13752             final org.apache.commons.jexl3.JexlEngine jexlEngine = new JexlBuilder().silent(silent).strict(!lenient).cache(cacheSize).create();
13753             jexlEngines3.put(new MultiKey(silent, lenient), jexlEngine);
13754           }
13755           {
13756             Boolean silent = false;
13757             Boolean lenient = true;
13758             final org.apache.commons.jexl3.JexlEngine jexlEngine = new JexlBuilder().silent(silent).strict(!lenient).cache(cacheSize).create();
13759             jexlEngines3.put(new MultiKey(silent, lenient), jexlEngine);
13760           }
13761           {
13762             Boolean silent = true;
13763             Boolean lenient = false;
13764             final org.apache.commons.jexl3.JexlEngine jexlEngine = new JexlBuilder().silent(silent).strict(!lenient).cache(cacheSize).create();
13765             jexlEngines3.put(new MultiKey(silent, lenient), jexlEngine);
13766           }
13767           {
13768             Boolean silent = false;
13769             Boolean lenient = false;
13770             final org.apache.commons.jexl3.JexlEngine jexlEngine = new JexlBuilder().silent(silent).strict(!lenient).cache(cacheSize).create();
13771             jexlEngines3.put(new MultiKey(silent, lenient), jexlEngine);
13772           }
13773           
13774           jexlEnginesInitialized3 = true;
13775         }
13776       }
13777     }
13778   }
13779 
13780   /**
13781    * get out of GSH with return code
13782    * @param returnCode
13783    */
13784   public static void gshReturn(int returnCode) {
13785     GrouperGroovyRuntime grouperGroovyRuntime = GrouperGroovyRuntime.retrieveGrouperGroovyRuntime();
13786     if (grouperGroovyRuntime == null) {
13787       // if in grouper shell
13788       throw new ExitNotification(returnCode);
13789     }
13790     // if in groovy
13791     grouperGroovyRuntime.gshReturn(returnCode);
13792   }
13793 
13794   /**
13795    * get out of gsh with normal return code
13796    */
13797   public static void gshReturn() {
13798     gshReturn(0);
13799   }
13800   
13801   /**
13802    * Checks to see if a specific port is available.
13803    *
13804    * @param port the port to check for availability
13805    * @param ipAddress
13806    * @return true if available
13807    */
13808   public static boolean portAvailable(int port, String ipAddress) {
13809 
13810     ServerSocket ss = null;
13811     try {
13812 
13813       if (isBlank(ipAddress) || "0.0.0.0".equals(ipAddress)) {
13814         ss = new ServerSocket(port);
13815       } else {
13816         
13817         Pattern pattern = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)");
13818         
13819         Matcher matcher = pattern.matcher(ipAddress);
13820         
13821         if (!matcher.matches()) {
13822           throw new RuntimeException("IP address not valid! '" + ipAddress + "'");
13823         }
13824         
13825         byte[] b = new byte[4];
13826         for (int i=0;i<4;i++) {
13827           int theInt = intValue(matcher.group(i+1));
13828           if (theInt > 255 || theInt < 0) {
13829             System.out.println("IP address part must be between 0 and 255: '" + theInt + "'");
13830           }
13831           b[i] = (byte)theInt;
13832         }
13833 
13834         //Returns an InetAddress object given the raw IP address .
13835         InetAddress inetAddress = InetAddress.getByAddress(b);
13836         
13837         ss = new ServerSocket(port, 50, inetAddress);
13838       }
13839       ss.setReuseAddress(true);
13840       return true;
13841     } catch (IOException e) {
13842     } finally {
13843       if (ss != null) {
13844         try {
13845           ss.close();
13846         } catch (IOException e) {
13847           /* should not be thrown */
13848         }
13849       }
13850     }
13851 
13852     return false;
13853   }
13854 
13855   /**
13856    * 
13857    * @param port
13858    * @param ipAddress
13859    * @param seconds
13860    * @param expectingListening
13861    * @return success
13862    */
13863   public static boolean portAvailableWait(int port, String ipAddress, int seconds, boolean expectingListening) {
13864     for (int i=0;i<60;i++) {
13865       GrouperUtil.sleep(1000);
13866       //check port
13867       boolean portAvailable = portAvailable(port, ipAddress);
13868       if (!expectingListening) {
13869         if (portAvailable) {
13870           return true;
13871         }
13872       } else {
13873         if (!portAvailable) {
13874           return true;
13875         }
13876       }
13877     }
13878     return false;
13879   }
13880   
13881   /**
13882    * see if we are running on windows
13883    * @return true if windows
13884    */
13885   public static boolean isWindows() {
13886     return GrouperClientUtils.isWindows();
13887   }  
13888 
13889 }