001    package edu.nrao.sss.util;
002    
003    import java.math.BigDecimal;
004    import java.math.RoundingMode;
005    import java.text.DecimalFormat;
006    import java.util.ArrayList;
007    import java.util.Collection;
008    import java.util.regex.Pattern;
009    import java.util.regex.PatternSyntaxException;
010    
011    import org.apache.axis.utils.XMLChar;
012    import org.apache.log4j.Logger;
013    
014    import edu.nrao.sss.math.MathUtil;
015    
016    /**
017     * A utility class for manipulating text strings.
018     * <p>
019     * <b>Version Info:</b>
020     * <table style="margin-left:2em">
021     *   <tr><td>$Revision: 1351 $</td>
022     *   <tr><td>$Date: 2008-06-13 14:11:55 -0600 (Fri, 13 Jun 2008) $</td>
023     *   <tr><td>$Author: dharland $</td>
024     * </table></p>
025     * 
026     * @author David M. Harland
027     * @since 2006-02-23
028     */
029    public class StringUtil
030    {
031            private static final Logger log = Logger.getLogger(StringUtil.class);
032    
033      /** Returns the end-of-line string. */
034      public static final String EOL = System.getProperty("line.separator");
035      
036      //This formatter is used so that we can avoid java's version of
037      //scientific notation.  NOTE: the documentation on this class
038      //mentions that it is not thread safe.  If we every multithread
039      //our stuff, we will need to change our usage here a bit.
040      private DecimalFormat numberFormatter = new DecimalFormat("0.0");
041      
042      private StringUtil()
043      {
044        numberFormatter.setMaximumFractionDigits(1000); //an arbitrarily high number
045      }
046    
047      private static StringUtil SINGLETON;
048    
049      /**
050       * Returns an instance of this string utility.
051       * 
052       * @return a string utility.
053       */
054      public static StringUtil getInstance()
055      {
056        //Just-in-time creation
057        if (SINGLETON == null)
058          SINGLETON = new StringUtil();
059        
060        return SINGLETON;
061      }
062    
063      /**
064       * Returns a normalized version of <code>str</code>.
065       * <p>
066       * Normalization means the following:
067       * <ol>
068       *   <li>If <code>str</code> is <i>null</i>,
069       *       the empty string (<code>""</code>) is returned.</li>
070       *   <li>Otherwise, all leading and trailing whitespace is removed.</li>
071       * </ol>
072       * @param str the string to be normalized.
073       * @return a normalized copy of <code>str</code>.
074       * @see java.lang.String#trim()
075       */
076      public String normalizeString(String str)
077      {
078        String nStr = "";
079        if (str != null)
080        {
081          nStr = str.trim();
082        }
083        return nStr;
084      }
085      
086      /**
087       * Returns a string based on {@code str} that is in lower case, except for
088       * the first letters of each word, which are in upper case.
089       * <p>
090       * <b><u>Example:</u></b><br/>
091       * <div style="margin-left:2em"><tt>
092       * INPUT:&nbsp; hANd OvER THe moNeY and NO OnE geTS HURT.<br/>
093       * OUTPUT: Hand Over The Money And No One Gets Hurt.
094       * </tt></div>
095       * 
096       * @param str the input string.
097       * 
098       * @return a string where each word is in lower case but begins with a
099       *         capital letter.
100       *         
101       * @see #capitalizeFirstLetterOfEachWord(String)
102       */
103      public String lowerAndCapitalizeFirstLetterOfEachWord(String str)
104      {
105        return capitalizeFirstLetterOfEachWord(str.toLowerCase());
106      }
107    
108      /**
109       * Returns a string based on {@code str} where
110       * the first letters of each word are converted to upper case.
111       * <p>
112       * <b><u>Example:</u></b><br/>
113       * <div style="margin-left : 2em"><tt>
114       * INPUT:&nbsp; hANd OvER THe moNeY and NO OnE geTS HURT.<br/>
115       * OUTPUT: HANd OvER THe MoNeY And NO OnE GeTS HURT.
116       * </tt></div>
117       * 
118       * @param str the input string.
119       * 
120       * @return a string where each word begins with a capital letter.
121       *         
122       * @see #lowerAndCapitalizeFirstLetterOfEachWord(String)
123       */
124      public String capitalizeFirstLetterOfEachWord(String str)
125      {
126        //Quick exit is input is null
127        if (str == null)
128          return null;
129        
130        StringBuilder buff = new StringBuilder(str);
131        
132        //Set to true so that we can capitalize first letter
133        boolean previousCharWasSpace = true;
134        
135        //Check each character.  We do this instead of splitting string on
136        //whitespace so that we keep original whitespace chars, including
137        //runs of consecutive white space chars.
138        for (int index = 0; index < buff.length(); index++)
139        {
140          char currChar = buff.charAt(index);
141          
142          //No need to capitalize unless char is not whitespace 
143          if (!Character.isWhitespace(currChar))
144          {
145            //Capitalize only when previous char was whitespace
146            if (previousCharWasSpace)
147            {
148              buff.setCharAt(index, Character.toUpperCase(currChar));
149            }
150            previousCharWasSpace = false;
151          }
152          //Curr char is whitespace
153          else
154          {
155            previousCharWasSpace = true;
156          }
157        }
158        
159        return buff.toString();
160      }
161    
162      /**
163             * @return a String containing multiplier consequitive tab characters.
164             */
165      public String getSpacer(int multiplier)
166      {
167              StringBuilder sp = new StringBuilder(multiplier);
168              for (int i = 0; i < multiplier; i++)
169                      sp.append("\t");
170    
171              return sp.toString();
172      }
173      
174      /**
175       * Returns a text representation of {@code number}.
176       * The returned text is similar to that provided by
177       * {@code Double.toString(double)}, except that the
178       * range of numbers where scientific notation is not
179       * used is given by the other parameters.
180       * <p>
181       * If {@code number} is such that<pre>
182       *   stdBeg <= number <= stdEnd
183       * </pre>
184       * scientific notation will not be used.</p>
185       * 
186       * @param number the number for which a text representation is needed.
187       * 
188       * @param stdBeg the minimum absolute value where regular notation
189       *               should be preferred to scientific notation.
190       *               This should be a non-negative number.
191       *               
192       * @param stdEnd the maximum absolute value where regular notation
193       *               should be preferred to scientific notation.
194       *               This should be a non-negative number.
195       * 
196       * @return a text representation of {@code number}.
197       */
198      public String format(double number, double stdBeg, double stdEnd)
199      {
200        double absVal = Math.abs(number);
201        
202        if ((stdBeg <= absVal) && (absVal <= stdEnd))
203          return numberFormatter.format(number);
204        else
205          return Double.toString(number);
206      }
207      
208      /**
209       * Returns a text representation of {@code number}.
210       * The returned text is similar to that provided by
211       * {@code Double.toString(double)}, except that
212       * scientific notation is never used.
213       * 
214       * @param number the number for which a text representation is needed.
215       * 
216       * @return a text representation of {@code number}.
217       */
218      public String formatNoScientificNotation(double number)
219      {
220        if (Double.isInfinite(number) || Double.isNaN(number))
221          return Double.toString(number);
222        else
223          return numberFormatter.format(number);
224      }
225      
226      /**
227       * Returns a text representation of {@code number}.
228       * The returned text is similar to that provided by
229       * {@code Double.toString(double)}, except that
230       * scientific notation is never used.
231       * 
232       * @param number the number for which a text representation is needed.
233       * 
234       * @param minFracDigits the minimum number of digits after the decimal point.
235       * 
236       * @param maxFracDigits the maximum number of digits after the decimal point.
237       * 
238       * @return a text representation of {@code number}.
239       */
240      public String formatNoScientificNotation(double number, int minFracDigits,
241                                                              int maxFracDigits)
242      {
243        String result;
244        
245        if (Double.isInfinite(number) || Double.isNaN(number))
246        {
247          result = Double.toString(number);
248        }
249        else
250        {
251          //Cache existing values, set new based on params
252          int oldMin = numberFormatter.getMinimumFractionDigits();
253          int oldMax = numberFormatter.getMaximumFractionDigits();
254          numberFormatter.setMinimumFractionDigits(minFracDigits);
255          numberFormatter.setMaximumFractionDigits(maxFracDigits);
256          
257          result = numberFormatter.format(number);
258          
259          //Restore original values
260          numberFormatter.setMinimumFractionDigits(oldMin);
261          numberFormatter.setMaximumFractionDigits(oldMax);
262        }
263        
264        return result;
265      }
266      
267      /**
268       * Returns a text representation of {@code number}.
269       * There will always be at least one digit after the decimal
270       * point.
271       * <p>
272       * If {@code number} is determined by
273       * {@link MathUtil#doubleValueIsInfinite(BigDecimal)} to
274       * be infinite, the returned text will be either
275       * "+infinity" or "-infinity".</p>
276       * 
277       * @param number the number for which a text representation is needed.
278       * 
279       * @return a text representation of {@code number}.
280       */
281      public String formatNoScientificNotation(BigDecimal number)
282      {
283        //Quick exit for infinite values
284        if (MathUtil.doubleValueIsInfinite(number))
285        {
286          return number.signum() > 0 ? "+infinity" : "-infinity";
287        }
288        
289        number = number.stripTrailingZeros();
290        
291        //The portion of the test after the || should not be necessary,
292        //but it is a work around for a BigDecimal bug reported here:
293        //http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6480539.
294        if (number.scale() < 1 || number.compareTo(BigDecimal.ZERO)==0)
295          number = number.setScale(1);
296        
297        return number.toPlainString();
298      }
299      
300      /**
301       * Returns a text representation of {@code number}.
302       * 
303       * @param number the number for which a text representation is needed.
304       * 
305       * @param minFracDigits the minimum number of digits after the decimal point.
306       * 
307       * @param maxFracDigits the maximum number of digits after the decimal point.
308       * 
309       * @return a text representation of {@code number}.
310       */
311      public String formatNoScientificNotation(BigDecimal number, int minFracDigits,
312                                                                  int maxFracDigits)
313      {
314        //Quick exit for infinite values
315        if (MathUtil.doubleValueIsInfinite(number))
316        {
317          return number.signum() > 0 ? "+infinity" : "-infinity";
318        }
319        
320        //Adjust the number's scale to fall w/in parameter range
321        number = number.stripTrailingZeros();
322        int scale = number.scale();
323    
324        //This if-statement should not be necessary,
325        //but it is a work around for a BigDecimal bug reported here:
326        //http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6480539.
327        if (number.compareTo(BigDecimal.ZERO)==0)
328          number = number.setScale(minFracDigits);
329      
330        if (scale < minFracDigits)
331        {
332          number = number.setScale(minFracDigits);
333        }
334        else if (scale > maxFracDigits)
335        {
336          number = number.setScale(maxFracDigits, RoundingMode.HALF_UP);
337        }
338        
339        return number.toPlainString();
340      }
341    
342      /**
343       * Returns a string that is the result of calling {@code toString()} on every
344       * member of {@code source} and inserting {@code separator} between each
345       * member.  A <i>null</i> member will be represented by the empty string.
346       * <p>
347       * The returned string does not contain a trailing separator
348       * (unless the final member of {@code source} was the empty string or ended
349       * with the separator).  This is a convenience method that is equivalent
350       * to calling {@link #fromCollection(Collection, String, boolean)
351       * fromCollection(source, separator, false)}.</p>
352       * <p>
353       * It is the client's duty to select a separator that will not be present
354       * in the string representation of any of the members of the source
355       * collection.</p>
356       * 
357       * @param source a collection of objects that will be turned into a single
358       *               string.
359       *               
360       * @param separator the text used to separate one member of the collection
361       *                  from another.
362       *                  
363       * @return a string representation of {@code source}.  If the collection is
364       *         empty, the empty string (<tt>""</tt>) is returned.
365       */
366      public String fromCollection(Collection<?> source, String separator)
367      {
368        return fromCollection(source, separator, false);
369      }
370      
371      /**
372       * Returns a string that is the result of calling {@code toString()} on every
373       * member of {@code source} and inserting {@code separator} between each
374       * member.  A <i>null</i> member will be represented by the empty string.
375       * <p>
376       * It is the client's duty to select a separator that will not be present
377       * in the string representation of any of the members of the source
378       * collection.</p>
379       * 
380       * @param source a collection of objects that will be turned into a single
381       *               string.
382       *               
383       * @param separator the text used to separate one member of the collection
384       *                  from another.
385       * 
386       * @param appendSeparatorToEnd if <i>true</i>, this method adds a final
387       *                             separator at the end of the returned string.
388       *                  
389       * @return a string representation of {@code source}.  If the collection is
390       *         empty, the empty string (<tt>""</tt>) is returned.
391       */
392      public String fromCollection(Collection<?> source, String separator,
393                                   boolean appendSeparatorToEnd)
394      {
395        StringBuilder buff = new StringBuilder();
396        
397        for (Object value : source)
398          buff.append(value==null ? "" : value.toString()).append(separator);
399        
400        //Remove the final separator?
401        if (!appendSeparatorToEnd)
402        {
403          int buffSize = buff.length();
404          if (buffSize > 0)
405          {
406            int separatorSize = separator.length();
407            buff.delete(buffSize-separatorSize, buffSize);
408          }
409        }
410        
411        return buff.toString();
412      }
413      
414      /**
415       * Returns a collection of strings that was built by parsing {@code source},
416       * using {@code separator} as a delimiter.
417       * <p>
418       * <b>Note:</b> the {@code separator} is <i>not</i> interpreted as a
419       * regular expression, but just as a simple string.  This allows a client
420       * to call {@code toString} and {@code fromString} with the same delimiter
421       * and obtain sensible results.  Also, a final delimiter is assumed, if
422       * not already present.  That is, using ";" as a delimiter, the following
423       * text would result in the set of integers from one through five:
424       * <tt>1;2;3;4;5</tt>.  Consecutive delimiters will result in empty-string
425       * (<tt>""</tt>) elements.
426       * This is a convenience method that is equivalent to calling
427       * {@link #toCollection(String, String, Collection, boolean)
428       * toCollection(source, separator, desination, true)}.</p>
429       * 
430       * @param source a string representation of a collection.
431       * 
432       * @param separator text that separates one member of a collection from
433       *                  another in the {@code source} text.
434       *                  
435       * @param destination the collection into which the members created from
436       *                    {@code source} are added.  If this parameter is
437       *                    <i>null</i>, a new collection will be created and
438       *                    used.
439       *                    
440       * @return a collection of strings built by parsing {@code source},
441       *         using {@code separator} as a delimiter.
442       */
443      public Collection<String> toCollection(String source, String separator, 
444                                             Collection<String> destination)
445      {
446        return toCollection(source, separator, destination, true);
447      }
448      
449      /**
450       * Returns a collection of strings that was built by parsing {@code source},
451       * using {@code separator} as a delimiter.
452       * <p>
453       * <b>Note:</b> the {@code separator} is <i>not</i> interpreted as a
454       * regular expression, but just as a simple string.  This allows a client
455       * to call {@code toString} and {@code fromString} with the same delimiter
456       * and obtain sensible results.
457       * Consecutive delimiters will result in empty-string (<tt>""</tt>)
458       * elements.</p>
459       * 
460       * @param source a string representation of a collection.
461       * 
462       * @param separator text that separates one member of a collection from
463       *                  another in the {@code source} text.
464       *                  
465       * @param destination the collection into which the members created from
466       *                    {@code source} are added.  If this parameter is
467       *                    <i>null</i>, a new collection will be created and
468       *                    used.
469       *                    
470       * @param addMissingFinalSeparator if <i>true</i>, any characters in
471       *          {@code source} between the final separator and the end of the
472       *          string will be treated as the final string added to
473       *          {@code destination}.  If <i>false</i>, any characters after
474       *          the final separator are discarded.
475       * 
476       * @return a collection of strings built by parsing {@code source},
477       *         using {@code separator} as a delimiter.
478       */
479      public Collection<String> toCollection(String source, String separator, 
480                                             Collection<String> destination,
481                                             boolean addMissingFinalSeparator)
482      {
483        if (destination == null)
484          destination = new ArrayList<String>();
485        
486        if (source != null)
487        {
488          StringBuilder buff   = new StringBuilder(source);
489          int           sepLen = separator.length();
490          int           sepPos;
491          
492          while ((sepPos = buff.indexOf(separator)) > -1)
493          {
494            destination.add(buff.substring(0, sepPos));
495            buff.delete(0, sepPos+sepLen);
496          }
497          
498          //Assume a final delimiter if there are characters remaining
499          if (addMissingFinalSeparator && (buff.length() > 0))
500            destination.add(buff.toString());
501        }
502        
503        return destination;
504      }
505      
506      //============================================================================
507      // XML Strings  (Create separate class?) 
508      //============================================================================
509      
510      /**
511       * Returns a valid XML Name by replacing illegal characters in {@code name}
512       * with {@code replacementForIllegalChars}.
513       * If there are no illegal characters, {@code name} is returned unaltered.
514       * If the first character of {@code name} is normally a legal value, but is
515       * not legal as the first character in an XML Name, the replacement
516       * character is <i>inserted</i> prior to the first character in
517       * {@code name}.
518       * 
519       * @param replacementForIllegalChars a character to use as a replacement for
520       *                                   illegal characters in {@code name}.
521       *                                   
522       * @return a valid XML name based on {@code name}.
523       */
524      public String toXmlName(String name, char replacementForIllegalChars)
525      {
526        String xmlName;
527        
528        if (!XMLChar.isValidName(name))
529        {
530          //Quick exit if replacement char is illegal
531          if (!XMLChar.isName(replacementForIllegalChars))
532            throwIllegalXmlChar(replacementForIllegalChars, "Name", false);
533          
534          StringBuilder buff = new StringBuilder(name);
535        
536          int charCount = name.length();
537          
538          if (charCount == 0)
539            throw new
540              IllegalArgumentException("Name must have at least one character.");
541          
542          //Special test for 1st char
543          if (!XMLChar.isNameStart(buff.charAt(0)))
544          {
545            //Make sure we can use the replacement as the 1st char
546            if (!XMLChar.isNameStart(replacementForIllegalChars))
547              throwIllegalXmlChar(replacementForIllegalChars, "Name", true);
548    
549            //If 1st char is not legal at all, replace it.
550            //Otherwise, insert replacement char at 1st position. 
551            if (!XMLChar.isName(buff.charAt(0)))
552              buff.setCharAt(0, replacementForIllegalChars);
553            else
554              buff.insert(0, replacementForIllegalChars);
555          }
556          
557          //Replace all illegal chars from 2nd position on
558          for (int i=1; i < charCount; i++)
559            if (!XMLChar.isName(buff.charAt(i)))
560              buff.setCharAt(i, replacementForIllegalChars);
561          
562          xmlName = buff.toString();
563        }
564        else
565        {
566          xmlName = name;
567        }
568        
569        return xmlName;
570      }
571      
572      /**
573       * Returns a valid XML non-colonized name by replacing illegal characters
574       * in {@code name} with {@code replacementForIllegalChars}.
575       * If there are no illegal characters, {@code name} is returned unaltered.
576       * If the first character of {@code name} is normally a legal value, but is
577       * not legal as the first character in an XML NCName, the replacement
578       * character is <i>inserted</i> prior to the first character in
579       * {@code name}.
580       * <p>
581       * XML Schema's ID and IDREF types are subtypes of NCName.  That is,
582       * any string used as an ID must be a legal NCName.</p>
583       * 
584       * @param replacementForIllegalChars a character to use as a replacement for
585       *                                   illegal characters in {@code name}.
586       *                                   
587       * @return a valid XML name based on {@code name}.
588       */
589      public String toXmlNCName(String name, char replacementForIllegalChars)
590      {
591        String xmlNCName;
592        
593        if (!XMLChar.isValidNCName(name))
594        {
595          //Quick exit if replacement char is illegal
596          if (!XMLChar.isNCName(replacementForIllegalChars))
597            throwIllegalXmlChar(replacementForIllegalChars, "NCName", false);
598    
599          StringBuilder buff = new StringBuilder(name);
600        
601          int charCount = name.length();
602          
603          if (charCount == 0)
604            throw new
605              IllegalArgumentException("Name must have at least one character.");
606          
607          //Special test for 1st char
608          if (!XMLChar.isNCNameStart(buff.charAt(0)))
609          {
610            //Make sure we can use the replacement as the 1st char
611            if (!XMLChar.isNCNameStart(replacementForIllegalChars))
612              throwIllegalXmlChar(replacementForIllegalChars, "NCName", true);
613    
614            //If 1st char is not legal at all, replace it.
615            //Otherwise, insert replacement char at 1st position. 
616            if (!XMLChar.isNCName(buff.charAt(0)))
617              buff.setCharAt(0, replacementForIllegalChars);
618            else
619              buff.insert(0, replacementForIllegalChars);
620          }
621          
622          //Replace all illegal chars from 2nd position on
623          for (int i=1; i < charCount; i++)
624            if (!XMLChar.isNCName(buff.charAt(i)))
625              buff.setCharAt(i, replacementForIllegalChars);
626          
627          xmlNCName = buff.toString();
628        }
629        else
630        {
631          xmlNCName = name;
632        }
633        
634        return xmlNCName;
635      }
636    
637      private void throwIllegalXmlChar(char c, String type, boolean start)
638      {
639        StringBuilder errMsg =
640          new StringBuilder("The value of replacementForIllegalChars, ");
641        
642        errMsg.append(c).append(", is not a legal character for ");
643        
644        if (start)
645          errMsg.append("the start of ");
646        
647        errMsg.append("an XML ").append(type).append('.');
648        
649        throw new IllegalArgumentException(errMsg.toString());
650      }
651      
652            /**
653       * Converts a glob expression to an SQL LIKE expression. For the purposes of
654       * this method, globs have 2 special operators:
655             * 
656             * <ul>
657             * <li><code>*</code> -- matches 0 or more of any character</li>
658             * <li><code>?</code> -- matches 1 of any character</li>
659             * </ul>
660             */
661            public String globToSqlLike(String glob, char escape)
662            {
663                    StringBuilder sql = new StringBuilder("");
664                    char[] chars = glob.toCharArray();
665    
666                    for (int i = 0; i < chars.length; i++)
667                    {
668                            //May be escaping a '*' or '?'
669                            if (chars[i] == '\\')
670                            {
671                                    if (i < (chars.length - 1) && (chars[i + 1] == '*' || chars[i + 1] == '?'))
672                                            i++;
673    
674            sql.append(chars[i]);
675                            }
676    
677                            else if (chars[i] == '%')
678                            {
679                                    sql.append(escape);
680                                    sql.append('%');
681                            }
682    
683                            else if (chars[i] == '_')
684                            {
685                                    sql.append(escape);
686                                    sql.append('_');
687                            }
688    
689                            else if (chars[i] == '*')
690                            {
691                                    sql.append("%");
692                            }
693    
694                            else if (chars[i] == '?')
695                            {
696                                    sql.append("_");
697                            }
698    
699                            else if (chars[i] == '\'')
700                            {
701                                    sql.append("''");
702                            }
703    
704                            else if (chars[i] == '"')
705                            {
706                                    sql.append("''''");
707                            }
708    
709                            else
710                            {
711                                    sql.append(chars[i]);
712                            }
713                    }
714    
715                    return sql.toString();
716      }
717    
718            /**
719             * Converts a glob expression to a regex. For the purposes of this method,
720             * globs have 2 special operators:
721             * 
722             * <ul>
723             * <li><code>*</code> -- matches 0 or more of any character</li>
724             * <li><code>?</code> -- matches 1 of any character</li>
725             * </ul>
726             *
727             * Note also that the returned regex will only match a whole string. (i.e.
728             * it starts with '^' and ends with '$')
729             *
730             * @return an regular expression equivalent to the glob <code>glob</code>.
731             */
732            public String globToRegex(String glob)
733            {
734                    StringBuilder regex = new StringBuilder("^");
735                    char[] chars = glob.toCharArray();
736    
737                    for (int i = 0; i < chars.length; i++)
738                    {
739                            //May be escaping a '*' or '?'
740                            if (chars[i] == '\\')
741                            {
742                                    if (i < (chars.length - 1) && (chars[i + 1] == '*' || chars[i + 1] == '?'))
743                                    {
744                                            i++;
745                                            regex.append("\\");
746                                            regex.append(chars[i]);
747                                    }
748    
749                                    else
750                                    {
751                                            regex.append("\\\\");
752                                    }
753                            }
754    
755                            else if (mustBeEscapedForRegex(chars[i]))
756                            {
757                                    regex.append("\\");
758                                    regex.append(chars[i]);
759                            }
760    
761                            else if (chars[i] == '*')
762                            {
763                                    regex.append(".*");
764                            }
765    
766                            else if (chars[i] == '?')
767                            {
768                                    regex.append(".");
769                            }
770    
771                            else
772                            {
773                                    regex.append(chars[i]);
774                            }
775                    }
776    
777                    regex.append("$");
778    
779                    return regex.toString();
780            }
781    
782            /**
783             * Calls {@link #globToRegex} to create a regex string, then compiles that regex and
784             * returns the resultant pattern. The Pattern has the CASE_INSENSITIVE and
785             * UNICODE_CASE flags set.
786             *
787             * @throws RuntimeException if the value returned by globToRegex is not a
788             * valid regex. Note: this should never happen but may if there's a
789             * programmer error in globToRegex.
790             */
791            public Pattern globToPattern(String glob)
792            {
793                    try
794                    {
795                            return Pattern.compile(
796                                    globToRegex(glob),
797                                    Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE
798                            );
799                    }
800                    catch (PatternSyntaxException e)
801                    {
802                            log.error("Programmer Error: ", e);
803                            throw new RuntimeException("Programmer Error: ", e);
804                    }
805            }
806    
807            /**
808             * Used by {@link #globToRegex(String)}. 
809             * @return true if the character {@code c} must be escaped in order to be
810             * added to a regular expression as a literal. NOTE: '\', '*', and '?' are not
811             * checked for here because they are treated specially in globToRegex() (i.e.
812             * not merely escaped).
813             */
814            private boolean mustBeEscapedForRegex(char c)
815            {
816                    final char[] replace = {'^', '$', '(', ')', '{', '}', '[', ']', '+', '|', '.'};
817    
818                    for (char r : replace)
819                    {
820                            if (c == r)
821                                    return true;
822                    }
823    
824                    return false;
825            }
826    
827      //============================================================================
828      // 
829      //============================================================================
830      /*
831      public static void main(String[] args) throws Exception
832      {
833        StringUtil util = StringUtil.getInstance();
834        char       c    = '_';
835        
836        String name = "abc123";
837        System.out.println("text="+name+", xs:Name="+util.toXmlName(name, c)+
838                                        ", xs:NCName="+util.toXmlNCName(name, c));
839        name = "abc  123";
840        System.out.println("text="+name+", xs:Name="+util.toXmlName(name, c)+
841                                        ", xs:NCName="+util.toXmlNCName(name, c));
842        name = "123abc";
843        System.out.println("text="+name+", xs:Name="+util.toXmlName(name, c)+
844                                        ", xs:NCName="+util.toXmlNCName(name, c));
845        name = "abc:123";
846        System.out.println("text="+name+", xs:Name="+util.toXmlName(name, c)+
847                                        ", xs:NCName="+util.toXmlNCName(name, c));
848        name = "abc&amp;123";
849        System.out.println("text="+name+", xs:Name="+util.toXmlName(name, c)+
850                                        ", xs:NCName="+util.toXmlNCName(name, c));
851        name = "abc:1:2.3";
852        System.out.println("text="+name+", xs:Name="+util.toXmlName(name, c)+
853                                        ", xs:NCName="+util.toXmlNCName(name, c));
854        name = "J0123+5432";
855        System.out.println("text="+name+", xs:Name="+util.toXmlName(name, c)+
856                                        ", xs:NCName="+util.toXmlNCName(name, c));
857        name = "J0123-5432";
858        System.out.println("text="+name+", xs:Name="+util.toXmlName(name, c)+
859                                        ", xs:NCName="+util.toXmlNCName(name, c));
860        
861        for (String arg : args)
862          System.out.println("text="+arg+", xs:Name="+util.toXmlName(arg, c)+
863                             ", xs:NCName="+util.toXmlNCName(arg, c));
864      }
865      */
866      //This is here for quick & dirty testing
867      /*
868      public static void main(String[] args)
869      {
870        double[] numbers = {0.0, -0.0, 0.00001, -0.00001, 123456789.876,
871                            1e19, 1e20, 1e21,
872                            1e-9, 1e-10, 1e-11,
873                            Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY,
874                            Double.NaN, Double.MAX_VALUE, Double.MIN_VALUE};
875        
876        for (double number : numbers)
877        {
878          System.out.println("java:         " + number);
879          System.out.println("format:       " + StringUtil.getInstance().format(number, 1e-10, 1e20));
880          System.out.println("format-no-SN: " + StringUtil.getInstance().formatNoScientificNotation(number));
881          System.out.println();
882        }
883        
884        System.out.println();
885        String msg = " hANd OvER THe   moNeY\t and NO OnE geTS HURT.";
886        System.out.println("INPUT:     " + msg);
887        System.out.println("OUTPUT 1:  " + StringUtil.getInstance().capitalizeFirstLetterOfEachWord(msg));
888        System.out.println("OUTPUT 2:  " + StringUtil.getInstance().lowerAndCapitalizeFirstLetterOfEachWord(msg));
889        msg = StringUtil.getInstance().normalizeString(msg);
890        System.out.println("OUTPUT 3:  " + StringUtil.getInstance().lowerAndCapitalizeFirstLetterOfEachWord(msg));
891      }
892      */
893      /*
894      public static void main(String[] args)
895      {
896        StringUtil util = StringUtil.getInstance();
897        
898        String text = "a;b;c;d;e;f";
899        System.out.println(util.toCollection(text, ";", null));
900        System.out.println(util.toCollection(text, ";", null, false));
901        System.out.println(util.toCollection(text, ";", null, true));
902        
903        String delim = " | ";
904        ArrayList<Integer> ints = new ArrayList<Integer>();
905        for (int i=1; i <= 20; i++)
906          ints.add(i);
907        System.out.println();
908        text = util.fromCollection(ints, delim);
909        System.out.println(text);
910        System.out.println(util.toCollection(text, delim, null));
911        System.out.println();
912        text = util.fromCollection(ints, delim, false);
913        System.out.println(text);
914        System.out.println(util.toCollection(text, delim, null, true));
915        System.out.println();
916        text = util.fromCollection(ints, delim, true);
917        System.out.println(text);
918        System.out.println(util.toCollection(text, delim, null, false));
919      }
920      */
921      /*
922      public static void main(String[] args)
923      {
924        Random generator = new Random();
925        
926        final int minFrac = 3;
927        final int maxFrac = 7;
928        
929        final StringUtil util = StringUtil.getInstance();
930        
931        for (int i=1; i < 50; i++)
932        {
933          double num = generator.nextDouble() * generator.nextInt();
934    
935          BigDecimal number = new BigDecimal(num);
936          number = number.setScale(generator.nextInt(8), RoundingMode.HALF_UP);
937          
938          System.out.print(util.formatNoScientificNotation(number, minFrac, maxFrac));
939          System.out.println(", " + number.toPlainString());
940        }
941      }
942      */
943    }