001package com.hfg.util;
002
003
004import java.text.DecimalFormat;
005import java.text.NumberFormat;
006import java.text.ParsePosition;
007import java.util.ArrayList;
008import java.util.HashMap;
009import java.util.HashSet;
010import java.util.List;
011import java.util.Locale;
012import java.util.Map;
013import java.util.Set;
014import java.util.regex.Pattern;
015import java.util.regex.Matcher;
016import java.util.Collection;
017
018import com.hfg.math.Range;
019import com.hfg.util.collection.CollectionUtil;
020
021//------------------------------------------------------------------------------
022/**
023 * General String utility functions.
024 *
025 * @author J. Alex Taylor, hairyfatguy.com
026 */
027//------------------------------------------------------------------------------
028// com.hfg XML/HTML Coding Library
029//
030// This library is free software; you can redistribute it and/or
031// modify it under the terms of the GNU Lesser General Public
032// License as published by the Free Software Foundation; either
033// version 2.1 of the License, or (at your option) any later version.
034//
035// This library is distributed in the hope that it will be useful,
036// but WITHOUT ANY WARRANTY; without even the implied warranty of
037// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
038// Lesser General Public License for more details.
039//
040// You should have received a copy of the GNU Lesser General Public
041// License along with this library; if not, write to the Free Software
042// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
043//
044// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
045// jataylor@hairyfatguy.com
046//------------------------------------------------------------------------------
047
048public class StringUtil
049{
050   private static final Pattern sWhitespacePattern         = Pattern.compile("[\\s\u00A0]+"); // \u00A0 is the non-breaking space character
051   private static final Pattern sTrailingWhitespacePattern = Pattern.compile("[\\s\u00A0]*$"); // \u00A0 is the non-breaking space character
052   private static final Pattern sSingleQuotePattern        = Pattern.compile("(^|[^\\\\])'");
053   private static final Pattern sDoubleQuotePattern        = Pattern.compile("(^|[^\\\\])\"");
054   private static final Pattern sContainsLowerCasePattern  = Pattern.compile("[a-z]");
055   private static final Pattern sContainsUpperCasePattern  = Pattern.compile("[A-Z]");
056
057   private static final String sLineSeparator = System.getProperty("line.separator");
058
059   private static Map<Character.UnicodeBlock, Range<Integer>> sUnicodeBlockRangeMap;
060   private static Map<Character.UnicodeScript, List<Integer>> sUnicodeScriptMap;
061
062   //**************************************************************************
063   // PUBLIC FUNCTIONS
064   //**************************************************************************
065
066   //---------------------------------------------------------------------------
067   public static String getSystemLineSeparator()
068   {
069      return sLineSeparator;
070   }
071
072   //---------------------------------------------------------------------------
073   /**
074    Returns whether the specified String contains non-whitespace content.
075    */
076   public static boolean isSet(CharSequence inString)
077   {
078      return (inString != null && inString.toString().trim().length() > 0);
079   }
080
081   //---------------------------------------------------------------------------
082   /**
083    Joins the elements of a String[] together using a space as the separator.
084    */
085   public static String join(String[] pieces)
086   {
087      return join(pieces, " ");
088   }
089
090   //---------------------------------------------------------------------------
091   /**
092    Joins the elements of a String[] together using the specified String.
093    */
094   public static String join(String[] pieces, String inSeperator)
095   {
096      StringBuffer buffer = new StringBuffer();
097
098      if (pieces != null)
099      {
100         for (int i = 0; i < pieces.length; i++)
101         {
102            if (i > 0) buffer.append(inSeperator);
103            buffer.append(pieces[i]);
104         }
105      }
106
107      return buffer.toString();
108   }
109
110   //---------------------------------------------------------------------------
111   /**
112    Joins the elements of an Object[] together using the specified String.
113    */
114   public static String join(Object[] inArray, String inSeperator)
115   {
116      StringBuffer buffer = new StringBuffer();
117
118      if (inArray != null)
119      {
120         for (int i = 0; i < inArray.length; i++)
121         {
122            if (i > 0) buffer.append(inSeperator);
123            buffer.append(inArray[i]);
124         }
125      }
126
127      return buffer.toString();
128   }
129
130   //---------------------------------------------------------------------------
131   /**
132    Joins the elements of a int[] together using the specified String.
133    */
134   public static String join(int[] inArray, String inSeperator)
135   {
136      StringBuffer buffer = new StringBuffer();
137
138      if (inArray != null)
139      {
140         for (int i = 0; i < inArray.length; i++)
141         {
142            if (i > 0) buffer.append(inSeperator);
143            buffer.append(inArray[i]);
144         }
145      }
146
147      return buffer.toString();
148   }
149
150   //---------------------------------------------------------------------------
151   /**
152    Joins the elements of a Collection together using the specified String.
153    */
154   public static String join(Collection pieces, String inSeperator)
155   {
156      StringBuilder buffer = null;
157
158      if (pieces != null)
159      {
160         for (Object obj : pieces)
161         {
162            if (buffer != null)
163            {
164               buffer.append(inSeperator);
165            }
166            else
167            {
168               buffer = new StringBuilder();
169            }
170            buffer.append(obj);
171         }
172      }
173
174      return buffer != null ? buffer.toString() : null;
175   }
176
177   //---------------------------------------------------------------------------
178   /**
179    Joins the elements of a Collection together using the specified String.
180    Enclose each element with single quotation marks.
181    Author: Esther Ting
182    */
183   public static String joinIncludeSingleQuote(Collection pieces, String inSeperator)
184   {
185      StringBuilder buffer = null;
186
187      if (pieces != null)
188      {
189         for (Object obj : pieces)
190         {
191            if (buffer != null)
192            {
193               buffer.append(inSeperator);
194            }
195            else
196            {
197               buffer = new StringBuilder();
198            }
199            buffer.append(StringUtil.singleQuote(obj));
200         }
201      }
202
203      return buffer != null ? buffer.toString() : null;
204   }
205
206
207   //---------------------------------------------------------------------------
208   /**
209    Returns a String composed of inString repeated inNum times.
210    */
211   public static String polyString(String inString, int inNum)
212   {
213      StringBuffer buffer = new StringBuffer(inNum);
214
215      for (int i = 0; i < inNum; i++)
216      {
217         buffer.append(inString);
218      }
219
220      return buffer.toString();
221   }
222
223   //---------------------------------------------------------------------------
224   /**
225    Returns a String composed of inChar repeated inNum times.
226    */
227   public static String polyChar(char inChar, int inNum)
228   {
229      StringBuffer buffer = new StringBuffer(inNum);
230
231      for (int i = 0; i < inNum; i++)
232      {
233         buffer.append(inChar);
234      }
235
236      return buffer.toString();
237   }
238
239   //---------------------------------------------------------------------------
240   /**
241    Returns a String where all instances of inTarget in inString are replaced by
242    inReplacement.
243    */
244   public static String replaceAll(CharSequence inString, String inTarget, String inReplacement)
245   {
246      String outString = null;
247      if (inString != null)
248      {
249         outString = inString.toString();
250
251         if (inTarget != null && inReplacement != null)
252         {
253            int     startFromIndex = inString.length();
254            int index = outString.lastIndexOf(inTarget, startFromIndex);
255
256            // Bail quickly if no replacements are necessary.
257            if (index >= 0)
258            {
259               StringBuilder buffer = new StringBuilder(outString);
260
261               do
262               {
263                  buffer.replace(index, index + inTarget.length(), inReplacement);
264                  startFromIndex = index - 1;
265               }
266               while ((index = outString.lastIndexOf(inTarget, startFromIndex)) >= 0);
267
268               outString = buffer.toString();
269            }
270         }
271      }
272
273      return outString;
274   }
275
276   //---------------------------------------------------------------------------
277   /**
278    Returns a String where all portions of inString matching the inTargetRegexp
279    are replaced by inReplacement.
280    */
281   public static String replaceAllRegexp(CharSequence inString, String inRegexpTarget, String inReplacement)
282   {
283      String outString = null;
284      if (inString != null)
285      {
286         outString = inString.toString();
287
288         if (inRegexpTarget != null
289               && inReplacement != null)
290         {
291            Pattern p = Pattern.compile(inRegexpTarget);
292
293            outString = replaceAllRegexp(inString, p, inReplacement);
294         }
295      }
296
297      return outString;
298   }
299
300   //---------------------------------------------------------------------------
301   /**
302    Returns a String where all portions of inString matching the inPattern
303    are replaced by inReplacement.
304    */
305   public static String replaceAllRegexp(CharSequence inString, Pattern inPattern, String inReplacement)
306   {
307      String outString = null;
308      if (inString != null)
309      {
310         outString = inString.toString();
311
312         if (inPattern != null
313               && inReplacement != null)
314         {
315            Matcher m = inPattern.matcher(inString);
316
317            outString = m.replaceAll(inReplacement);
318         }
319      }
320
321      return outString;
322   }
323
324   //---------------------------------------------------------------------------
325   public static String trimTrailingWhitespace(CharSequence inString)
326   {
327      return replaceAllRegexp(inString, sTrailingWhitespacePattern, "");
328   }
329
330   //---------------------------------------------------------------------------
331   public static String removeWhitespace(CharSequence inString)
332   {
333      return replaceAllRegexp(inString, sWhitespacePattern, "");
334   }
335
336   //---------------------------------------------------------------------------
337   public static String replaceWhitespace(CharSequence inString, CharSequence inWhitespaceReplacementString)
338   {
339      return replaceAllRegexp(inString, sWhitespacePattern, inWhitespaceReplacementString != null ? inWhitespaceReplacementString.toString() : "");
340   }
341
342   //---------------------------------------------------------------------------
343   /**
344    Returns the specified String surrounded by quotes and escaping any internal quotes.
345    */
346   public static String quote(Object inObject)
347   {
348      StringBuilder buffer = new StringBuilder("\"");
349      if (inObject != null)
350      {
351         buffer.append(replaceAllRegexp(inObject.toString(), sDoubleQuotePattern, "$1\\\\\""));
352      }
353
354      buffer.append("\"");
355      return buffer.toString();
356   }
357
358
359   //---------------------------------------------------------------------------
360   /**
361    Returns the specified String surrounded by single quotes and escaping any internal single quotes.
362    */
363   public static String singleQuote(Object inObject)
364   {
365      StringBuilder buffer = new StringBuilder("'");
366      if (inObject != null)
367      {
368         buffer.append(replaceAllRegexp(inObject.toString(), sSingleQuotePattern, "$1\\\\'"));
369      }
370
371      buffer.append("'");
372      return buffer.toString();
373   }
374
375   //---------------------------------------------------------------------------
376   /**
377    Returns whether or not the specified String is contained within surrounding quotes.
378    */
379   public static boolean isQuoted(CharSequence inString)
380   {
381      boolean result = false;
382      if (inString != null)
383      {
384         String string = inString.toString();
385         result = ((string.startsWith("\"")
386               && string.endsWith("\""))
387               || (string.startsWith("\'")
388               && string.endsWith("\'")));
389      }
390
391      return result;
392   }
393
394   //---------------------------------------------------------------------------
395   /**
396    Returns the specified String while removing surrounding quotes if present.
397    */
398   public static String unquote(CharSequence inString)
399   {
400      String result = null;
401      if (inString != null)
402      {
403         result = inString.toString();
404
405         if (isQuoted(result))
406         {
407            result = result.substring(1, result.length() - 1);
408         }
409      }
410
411      return result;
412   }
413
414   //---------------------------------------------------------------------------
415   /**
416    Returns the number of times the specified character appears in the specified String.
417    */
418   public static int getCharCount(CharSequence inString, char inChar)
419   {
420      int count = 0;
421
422      if (isSet(inString))
423      {
424         for (int i = 0; i < inString.length(); i++)
425         {
426            if (inString.charAt(i) == inChar)
427            {
428               count++;
429            }
430         }
431      }
432
433      return count;
434   }
435
436   //---------------------------------------------------------------------------
437   public static String scramble(CharSequence inString)
438   {
439      char[] chars = inString.toString().toCharArray();
440      ArrayUtil.shuffle(chars);
441      return new String(chars);
442   }
443
444   //---------------------------------------------------------------------------
445   public static String applySubstitutionMap(CharSequence inString, Map<String, String> inSubstitutionMap)
446   {
447      String substitutedString = inString.toString();
448
449      for (String key : inSubstitutionMap.keySet())
450      {
451         substitutedString = substitutedString.replaceAll(key, inSubstitutionMap.get(key));
452      }
453
454      return substitutedString;
455   }
456
457   //---------------------------------------------------------------------------
458   public static String applySubstitutionMap(CharSequence inString, Map<String, String> inSubstitutionMap, String inDelimiter)
459   {
460      StringBuilder buffer = new StringBuilder(inString.toString());
461
462      Pattern pattern = Pattern.compile("(" + inDelimiter + "(.+?)" + inDelimiter + ")");
463
464      Set<String> unsubstitutedTokens = new HashSet<>(25);
465
466      Matcher m = pattern.matcher(buffer);
467      int index = 0;
468      while (m.find(index))
469      {
470         String token = m.group(2);
471         if (inSubstitutionMap.containsKey(token))
472         {
473            String replacementString = inSubstitutionMap.get(token);
474
475            buffer.replace(m.start(1), m.end(1), replacementString);
476
477            index = m.start(1) + replacementString.length();
478         }
479         else
480         {
481            unsubstitutedTokens.add(token);
482            index = m.end(1) + 1;
483         }
484      }
485
486      if (CollectionUtil.hasValues(unsubstitutedTokens))
487      {
488         throw new RuntimeException("Unsubstituted tokens: [" + StringUtil.join(unsubstitutedTokens, ", ") + "]!");
489      }
490
491      return buffer.toString();
492   }
493
494   //---------------------------------------------------------------------------
495   public static String stripHTMLTags(String inString)
496   {
497      return (inString != null ? inString.replaceAll("\\<.*?\\>", "") : null);
498   }
499
500   //---------------------------------------------------------------------------
501   /**
502    If the specified string is longer than the specified maximum length, a truncated
503    string is returned. Otherwise the original string is returned.
504    @param inString the original string value
505    @param inMaxLength the maximum lenght allowed for the string
506    @return the processed string value
507    */
508   public static String truncate(String inString, int inMaxLength)
509   {
510      if (inMaxLength < 0)
511      {
512         inMaxLength = 0;
513      }
514
515      return (inString != null ? (inString.length() > inMaxLength ? inString.substring(0, inMaxLength) : inString) : null);
516   }
517
518   //---------------------------------------------------------------------------
519   /**
520    Truncates the string and adds an ellipsis (…) if it exceeds the specified length.
521    Otherwise the original string is returned.
522    @param inString the string to be truncated
523    @param inMaxLength the length beyond which the specified string should be truncated
524    @return  the truncated string
525    */
526   public static String truncateWithEllipsis(String inString, int inMaxLength)
527   {
528      String result = null;
529
530      if (StringUtil.isSet(inString))
531      {
532         if (inString.length() <= inMaxLength)
533         {
534            // No need to truncate
535            result = inString;
536         }
537         else
538         {
539            result = inString.substring(0, inMaxLength) + '…';
540         }
541      }
542
543      return result;
544   }
545
546   //---------------------------------------------------------------------------
547   /**
548    Returns whether or not the specified string contains a lower-case letter.
549    @param inString the string value to be checked
550    @return whether or not the specified string contains a lower-case letter
551    */
552   public static boolean containsLowerCase(String inString)
553   {
554      return (inString != null ? sContainsLowerCasePattern.matcher(inString).find() : false);
555   }
556
557   //---------------------------------------------------------------------------
558   /**
559    Returns whether or not the specified string contains an upper-case letter.
560    @param inString the string value to be checked
561    @return whether or not the specified string contains an upper-case letter
562    */
563   public static boolean containsUpperCase(String inString)
564   {
565      return (inString != null ? sContainsUpperCasePattern.matcher(inString).find() : false);
566   }
567
568   //---------------------------------------------------------------------------
569   /**
570    Returns the input string with the first character capitalized.
571    @param inString the string value to be capitalized
572    @return  the input string with the first character capitalized
573    */
574   public static String capitalize(String inString)
575   {
576      return (isSet(inString) ? Character.toUpperCase(inString.charAt(0)) + (inString.length() > 1 ? inString.substring(1) : "") : inString);
577   }
578
579
580   //---------------------------------------------------------------------------
581   /**
582    Returns a unicode string for the specified number as a superscript.
583    @param inNumericValue the numeric value to be superscripted
584    @return  the unicode string for the specified number as a superscript
585    */
586   public static String toSuperscript(int inNumericValue)
587   {
588      return toSuperscript(inNumericValue + "");
589   }
590
591   //---------------------------------------------------------------------------
592   /**
593    Returns a unicode string for the specified number as a superscript.
594    @param inString the numeric value to be superscripted
595    @return  the unicode string for the specified number as a superscript
596    */
597   public static String toSuperscript(String inString)
598   {
599      StringBuilder buffer = new StringBuilder();
600      for (int i = 0; i < inString.length(); i++)
601      {
602         char superscript;
603         switch (inString.charAt(i))
604         {
605            case '0':
606               superscript = '\u2070';
607               break;
608            case '1':
609               superscript = 0xB9;
610               break;
611            case '2':
612               superscript = 0xB2;
613               break;
614            case '3':
615               superscript = 0xB3;
616               break;
617            case '4':
618               superscript = '\u2074';
619               break;
620            case '5':
621               superscript = '\u2075';
622               break;
623            case '6':
624               superscript = '\u2076';
625               break;
626            case '7':
627               superscript = '\u2077';
628               break;
629            case '8':
630               superscript = '\u2078';
631               break;
632            case '9':
633               superscript = '\u2079';
634               break;
635            case '+':
636               superscript = '\u207A';
637               break;
638            case '-':
639               superscript = '\u207B';
640               break;
641            case '=':
642               superscript = '\u207C';
643               break;
644            case '(':
645               superscript = '\u207D';
646               break;
647            case ')':
648               superscript = '\u207E';
649               break;
650            case 'n':
651               superscript = '\u207F';
652               break;
653            default:
654               throw new RuntimeException("Unsupported superscript char: " + StringUtil.singleQuote(inString.charAt(i)) + "!");
655         }
656
657         buffer.append(superscript);
658      }
659
660      return buffer.toString();
661   }
662
663   //---------------------------------------------------------------------------
664   /**
665    Returns a unicode string for the specified number as a subscript.
666    @param inNumericValue the numeric value to be subscripted
667    @return  the unicode string for the specified number as a subscript
668    */
669   public static String toSubscript(int inNumericValue)
670   {
671      return toSubscript(inNumericValue + "");
672   }
673
674   //---------------------------------------------------------------------------
675   /**
676    Returns a unicode string for the specified number as a subscript.
677    @param inString the numeric value to be subscripted
678    @return  the unicode string for the specified number as a subscript
679    */
680   public static String toSubscript(String inString)
681   {
682      StringBuilder buffer = new StringBuilder();
683      for (int i = 0; i < inString.length(); i++)
684      {
685         char subscript;
686         switch (inString.charAt(i))
687         {
688            case '0':
689               subscript = '\u2080';
690               break;
691            case '1':
692               subscript = '\u2081';
693               break;
694            case '2':
695               subscript = '\u2082';
696               break;
697            case '3':
698               subscript = '\u2083';
699               break;
700            case '4':
701               subscript = '\u2084';
702               break;
703            case '5':
704               subscript = '\u2085';
705               break;
706            case '6':
707               subscript = '\u2086';
708               break;
709            case '7':
710               subscript = '\u2087';
711               break;
712            case '8':
713               subscript = '\u2088';
714               break;
715            case '9':
716               subscript = '\u2089';
717               break;
718            case '+':
719               subscript = '\u208A';
720               break;
721            case '-':
722               subscript = '\u208B';
723               break;
724            case '=':
725               subscript = '\u208C';
726               break;
727            case '(':
728               subscript = '\u208D';
729               break;
730            case ')':
731               subscript = '\u208E';
732               break;
733            case 'a':
734               subscript = '\u2090';
735               break;
736            case 'e':
737               subscript = '\u2091';
738               break;
739            case 'o':
740               subscript = '\u2092';
741               break;
742            case 'x':
743               subscript = '\u2093';
744               break;
745            case 'Ι™':
746               subscript = '\u2094';
747               break;
748            case 'h':
749               subscript = '\u2095';
750               break;
751            case 'k':
752               subscript = '\u2096';
753               break;
754            case 'l':
755               subscript = '\u2097';
756               break;
757            case 'm':
758               subscript = '\u2098';
759               break;
760            case 'n':
761               subscript = '\u2099';
762               break;
763            case 'p':
764               subscript = '\u209A';
765               break;
766            case 's':
767               subscript = '\u209B';
768               break;
769            case 't':
770               subscript = '\u209C';
771               break;
772            default:
773               throw new RuntimeException("Unsupported subscript char: " + StringUtil.singleQuote(inString.charAt(i)) + "!");
774         }
775
776         buffer.append(subscript);
777      }
778
779      return buffer.toString();
780   }
781
782   //---------------------------------------------------------------------------
783   public static String[] lines(CharSequence inString)
784   {
785      return inString != null ? inString.toString().split("\\r?\\n") : null;
786   }
787
788   //---------------------------------------------------------------------------
789   public static String wrap(String inString, int inMaxLineLength)
790   {
791      StringBuilderPlus buffer = null;
792      if (inString !=  null)
793      {
794         buffer = new StringBuilderPlus(inString);
795
796         int index = inMaxLineLength;
797         while (index < buffer.length())
798         {
799            for (int i = index; i > index - inMaxLineLength; i--)
800            {
801               char theChar = buffer.charAt(i);
802
803               if (theChar == ' ')
804               {
805                  buffer.replace(i, i + 1, sLineSeparator);
806                  index = i + inMaxLineLength + 1;
807               }
808               else if (theChar == ','
809                        || theChar == ';'
810                        || theChar == '.')
811               {
812                  buffer.insert(i + 1, sLineSeparator);
813                  index = i + inMaxLineLength + 1;
814               }
815            }
816         }
817
818      }
819
820      return buffer != null ? buffer.toString() : null;
821   }
822
823   //---------------------------------------------------------------------------
824   /**
825    Returns a random string of the specified length using the specified alphabet of characters.
826    @param inAlphabet a string of the allowed characters
827    @param inLength the length of the random string to be composed
828    @return  the random string 
829    */
830   public static String generateRandomString(CharSequence inAlphabet, int inLength)
831   {
832      StringBuilderPlus buffer = new StringBuilderPlus();
833      for (int i = 0; i < inLength; i++)
834      {
835         buffer.append(inAlphabet.charAt((int) Math.floor(Math.random() * inAlphabet.length())));
836      }
837
838      return buffer.toString();
839   }
840
841   //---------------------------------------------------------------------------
842   public static String generateRandomString(Character.UnicodeScript inScript, int inLength)
843   {
844      List<Integer> unicodeCodePoints = getCharactersForUnicodeScript(inScript);
845
846      StringBuilderPlus buffer = new StringBuilderPlus();
847      for (int i = 0; i < inLength; i++)
848      {
849         int decimalValue = CollectionUtil.getRandomListItem(unicodeCodePoints);
850
851         buffer.append(new String(Character.toChars(decimalValue)));
852      }
853
854      return buffer.toString();
855   }
856
857   //---------------------------------------------------------------------------
858   // https://en.wikipedia.org/wiki/Template:Unicode_chart_Egyptian_Hieroglyphs
859   // π“€π“ƒ π“ˆ·π“ŽΌπ“„Šπ“–π“‹Šπ“‡šπ“‹»π“°π“ͺπ“‹Όπ“Š π“‡™π“ˆ“π“„π“‰¨π“Ήπ“€»π“π“‡»π“‹¨π“ˆ³π“Š„π“„―π“ˆΊ
860   public static String generateRandomEgyptianHieroglyphics(int inLength)
861   {
862      return generateRandomString(Character.UnicodeScript.EGYPTIAN_HIEROGLYPHS, inLength);
863   }
864
865
866   //---------------------------------------------------------------------------
867   public static synchronized Range<Integer> getRangeForUnicodeBlock(Character.UnicodeBlock inBlock)
868   {
869      if (null == sUnicodeBlockRangeMap)
870      {
871         determineUnicodeBlockRanges();
872      }
873
874      return sUnicodeBlockRangeMap.get(inBlock);
875   }
876
877   //---------------------------------------------------------------------------
878   public static synchronized List<Integer> getCharactersForUnicodeScript(Character.UnicodeScript inScript)
879   {
880      if (null == sUnicodeScriptMap)
881      {
882         buildUnicodeScriptMap();
883      }
884
885      return sUnicodeScriptMap.get(inScript);
886   }
887
888   //---------------------------------------------------------------------------
889   // Use for formatting large numbers with commas. Ex: 20,512 instead of 20512
890   public static synchronized String generateLocalizedNumberString(Number inValue)
891   {
892      return generateLocalizedNumberString(inValue, Locale.getDefault());
893   }
894
895   //---------------------------------------------------------------------------
896   // Use for formatting large numbers with commas. Ex: 20,512 instead of 20512
897   public static synchronized String generateLocalizedNumberString(Number inValue, Locale inLocale)
898   {
899      return inValue != null ? NumberFormat.getInstance(inLocale).format(inValue) : null;
900   }
901
902   //---------------------------------------------------------------------------
903   public static boolean isNumber(CharSequence inString)
904   {
905      boolean result = false;
906      if (inString != null)
907      {
908         String s = inString.toString().trim();
909
910         try
911         {
912            Double.parseDouble(s);
913            result = true;
914         }
915         catch (NumberFormatException e)
916         {
917            // Attempt to deal with localized number strings
918            NumberFormat format = DecimalFormat.getInstance();
919            ParsePosition parsePosition = new ParsePosition(0);
920
921            format.parse(s, parsePosition);
922            if (parsePosition.getIndex() == s.length())
923            {
924               result = true;
925            }
926         }
927      }
928
929      return result;
930   }
931
932   //---------------------------------------------------------------------------
933   public static boolean allValuesAreEmpty(String[] inArray)
934   {
935      boolean empty = true;
936
937      if (inArray != null)
938      {
939         for (String value : inArray)
940         {
941            if (StringUtil.isSet(value))
942            {
943               empty = false;
944               break;
945            }
946         }
947      }
948
949      return empty;
950   }
951
952   //---------------------------------------------------------------------------
953   /**
954    * Calculates the Levenshtein distance between two Strings.
955    * Algorithm based on Apache Commons implementation.
956    * @param inString1 the first string for comparison
957    * @param inString2 the second string for comparison
958    * @return the computed Levenshtein distance
959    */
960   public static int computeLevenshteinDistance(CharSequence inString1, CharSequence inString2)
961   {
962      int distance = 0;
963
964      if (null == inString1)
965      {
966         if (inString2 != null)
967         {
968            distance = inString2.length();
969         }
970      }
971      else if (null == inString2)
972      {
973         distance = inString1.length();
974      }
975      else
976      {
977         // Both strings not null
978
979         // To simplify, string 1 should be the shorter of the two
980         if (inString1.length() > inString2.length())
981         {
982            // Swap
983            CharSequence tmp = inString1;
984            inString1 = inString2;
985            inString2 = tmp;
986         }
987
988         final int length1 = inString1.length();
989         final int length2 = inString2.length();
990
991         final int[] scoreArray = new int[length1 + 1];
992
993         int upperLeft;
994         int upper;
995         char string2J;
996         int ijComparison;
997
998         // Initialize the scoreArray
999         for (int i = 0; i <= length1; i++)
1000         {
1001            scoreArray[i] = i;
1002         }
1003
1004         // Evaluate
1005         for (int j = 1; j <= length2; j++)
1006         {
1007            upperLeft = scoreArray[0];
1008            string2J = inString2.charAt(j - 1);
1009            scoreArray[0] = j;
1010
1011            for (int i = 1; i <= length1; i++)
1012            {
1013               upper = scoreArray[i];
1014               ijComparison = inString1.charAt(i - 1) == string2J ? 0 : 1;
1015               scoreArray[i] = Math.min(Math.min(scoreArray[i - 1] + 1, scoreArray[i] + 1), upperLeft + ijComparison);
1016               upperLeft = upper;
1017            }
1018         }
1019
1020         distance = scoreArray[length1];
1021      }
1022
1023      return distance;
1024   }
1025
1026   //**************************************************************************
1027   // PRIVATE FUNCTIONS
1028   //**************************************************************************
1029
1030   //---------------------------------------------------------------------------
1031   private static void determineUnicodeBlockRanges()
1032   {
1033      sUnicodeBlockRangeMap = new HashMap<>(300);
1034
1035      Character.UnicodeBlock currentUnicodeBlock = null;
1036      Range<Integer> currentCodePointRange = null;
1037      for (int decimalValue = Character.MIN_CODE_POINT; decimalValue <= Character.MAX_CODE_POINT; decimalValue++)
1038      {
1039         Character.UnicodeBlock unicodeBlock = Character.UnicodeBlock.of(decimalValue);
1040         if (null == currentUnicodeBlock
1041             || ! currentUnicodeBlock.equals(unicodeBlock))
1042         {
1043             if (currentCodePointRange != null)
1044             {
1045                currentCodePointRange.setEnd(decimalValue - 1);
1046                sUnicodeBlockRangeMap.put(currentUnicodeBlock, currentCodePointRange);
1047             }
1048
1049             currentUnicodeBlock = unicodeBlock;
1050             currentCodePointRange = new Range<>();
1051             currentCodePointRange.setStart(decimalValue);
1052         }
1053      }
1054
1055      currentCodePointRange.setEnd(Character.MAX_CODE_POINT);
1056      sUnicodeBlockRangeMap.put(currentUnicodeBlock, currentCodePointRange);
1057   }
1058
1059   //---------------------------------------------------------------------------
1060   private static void buildUnicodeScriptMap()
1061   {
1062      sUnicodeScriptMap = new HashMap<>(300);
1063
1064      for (int decimalValue = Character.MIN_CODE_POINT; decimalValue <= Character.MAX_CODE_POINT; decimalValue++)
1065      {
1066         Character.UnicodeScript unicodeScript = Character.UnicodeScript.of(decimalValue);
1067         if (unicodeScript != null)
1068         {
1069            List<Integer> codePoints = sUnicodeScriptMap.get(unicodeScript);
1070            if (null == codePoints)
1071            {
1072               codePoints = new ArrayList<>();
1073               sUnicodeScriptMap.put(unicodeScript, codePoints);
1074            }
1075
1076            codePoints.add(decimalValue);
1077         }
1078      }
1079
1080      // Save memory by trimming the character lists to size
1081      for (Character.UnicodeScript script : sUnicodeScriptMap.keySet())
1082      {
1083         ((ArrayList) sUnicodeScriptMap.get(script)).trimToSize();
1084      }
1085   }
1086}