package nxm.sys.lib;

import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.text.AttributedCharacterIterator;
import java.text.FieldPosition;
import java.text.Format;
import java.text.ParseException;
import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.regex.Pattern;

import nxm.sys.inc.AsciiMap;
import nxm.sys.inc.InternalUseOnly;
import nxm.sys.inc.UnicodeMap;

/** Defines useful string utility functions.

    @author  J. Czechowski IV, J. Hallam, Neon Ngo
    @since   NeXtMidas 1.9.0
 */
public class StringUtil implements AsciiMap {
  /** Prevent instantiation since all methods are static. */
  private StringUtil () { } // private since 3.7.0; it was deprecated in 3.3.0

  /** FYI: zero-length arrays are immutable. For more info see Effective Java,
      Item 27: Return zero-length arrays, not null. */
  public static final String[] EMPTY_STRING_ARRAY = new String[0];
  private static final List<String>      EMPTY_LIST = Collections.emptyList();
  private static final SortedSet<String> EMPTY_SET  = Collections.emptySortedSet();
  /** comma (',') regular expression (compiled) */
  public static final Pattern COMMA_REGEX = Pattern.compile(",");
  /** comma (',') or whitespace (space,tab,etc.) regular expression (compiled). */
  public static final Pattern COMMA_OR_WHITESPACE_REGEX = Pattern.compile("[,\\s]+"); // since 3.1.2
  /** all common Midas separators: comma (','), pipe (i.e. vertical bar, '|'),
      or whitespace (space,tab,etc.) regular expression (compiled)
   */
  public static final Pattern SEPATORS_REGEX = Pattern.compile("[,|\\s]+"); // since 3.1.2
  /** one or more white spaces (space,tab,return,newline) regular expression (compiled) */
  public static final Pattern WHITESPACE_REGEX = Pattern.compile("\\s+"); // since 3.3.0

  /** Regular expression pattern for valid TAG NAME (i.e. key name) in Table that is also XML safe. */
  public static final Pattern TABLE_AND_XML_TAG_NAME_REGEX  = Pattern.compile("^[A-Z][0-9A-Z_]*"); // since 2.7.4
  /** Regular expression pattern for invalid TAG NAME (i.e. key name) in Table or XML element type tag names.
      This can be used to strip out invalid characters by doing the following:
      <code>StringUtil.TABLE_AND_XML_TAG_NAME_STRIP_INVALID_REGEX.matcher(str).replaceAll("");</code>
      Note: This does not account for "custom" XML element type tags that start with "xml" (upper or lower case),
            which are invalid according to the XML specifications as those tags are reserved.
   */
  public static final Pattern TABLE_AND_XML_TAG_NAME_STRIP_INVALID_REGEX  = Pattern.compile("[^0-9A-Z_]"); // since 2.7.4

  private static final Pattern LTRIM_REGEX = Pattern.compile("^\\s+"); // for ltrim()
  private static final Pattern RTRIM_REGEX = Pattern.compile("\\s+$"); // for rtrim()

  private static final Pattern HEX_VAL_REGEX = Pattern.compile("\\p{XDigit}+");
  private static final Pattern INTEGER_REGEX = Pattern.compile("[-]?\\p{Digit}+");
  /** Float regex (starts with num) */
  private static final Pattern FLOAT1_REGEX = Pattern.compile("[-]?\\p{Digit}+([.]\\p{Digit}*)?([eE][-+]?\\p{Digit}+)?");
  /** Float regex (starts with .num) */
  private static final Pattern FLOAT2_REGEX = Pattern.compile("[-]?[.]\\p{Digit}+([eE][-+]?\\p{Digit}+)?");

  // pre-compiled patterns to escape text to HTML
  private static final Pattern AMP_REGEX   = Pattern.compile("&");  // &
  private static final Pattern LT_REGEX    = Pattern.compile("<");  // <
  private static final Pattern GT_REGEX    = Pattern.compile(">");  // >
  private static final Pattern QUOTE_REGEX = Pattern.compile("\""); // "

  // pre-compiled patterns to convert HTML to text
  private static final Pattern HTML2SPACES_REGEX =  Pattern.compile(
      "(?i)" +        // case insensitive
      "<dd>" +        // definition description tag
      "|&nbsp;" +     // non-breaking space
      "|\t"           // tab
    );

  private static final Pattern HTML2NEWLINE_REGEX =  Pattern.compile(
      "(?i)" +        // case insensitive
      "\\s*\n" +      // all spaces (space,tab,return,newline) before newline
      "|<dt>" +       // definition term tag
      "|<br */?>" +  // carriage return (i.e. break) tag
      "|<tr>"         // new table row tag
    );

  private static final Pattern HTML_TAGS_REGEX  = Pattern.compile("<[\\p{Alpha}/!][^>]*>"); // html tags
  private static final Pattern HTML_LT_REGEX    = Pattern.compile("(?i)&lt;");    // <
  private static final Pattern HTML_GT_REGEX    = Pattern.compile("(?i)&gt;");    // >
  private static final Pattern HTML_AMP_REGEX   = Pattern.compile("(?i)&amp;");   // &
  private static final Pattern HTML_QUOTE_REGEX = Pattern.compile("(?i)&quot;");  // "
  private static final Pattern HTML_COPY_REGEX  = Pattern.compile("(?i)&copy;");  // [Copyright]
  private static final Pattern HTML_REG_REGEX   = Pattern.compile("(?i)&reg;");   // [Registered]
  private static final Pattern HTML_TRADE_REGEX = Pattern.compile("(?i)&trade;"); // [Trademark]

  /** the escape character is the backslash */
  public static final char ESCAPE_CHAR = BSLASH;

  /** {@link #expandVariables(String, Map, int)} bit flag option indicating that no error
      (MidasException) should be thrown when input text string for variable substitution
      contains mismatched braces. I.e. it will ignore and continue. value = {@value}.
      @since NeXtMidas 3.1.2
   */
  public static final int NO_ERROR = 0x1;

  /** Is the given char a digit between '0' and '9' (inclusive)? */
  private static boolean isDigit09 (char c) {
    return (c >= '0') && (c <= '9');
  }

  /** A {@link Comparator} to use for names, such as table key names or file
      names that uses an integer comparison for any numeric parts such that
      a name like "ABC9" comes before "ABC10". Examples:
      <pre>
        NAME_COMPARATOR.compare("ABC",   "XYZ"  )  --&gt;  -1
        NAME_COMPARATOR.compare("123",   "456"  )  --&gt;  -1
        NAME_COMPARATOR.compare("X007",  "X7"   )  --&gt;   0  ("X"="X" and 007 = 7)
        NAME_COMPARATOR.compare("ABC9",  "ABC10")  --&gt;  -1
        NAME_COMPARATOR.compare("A9B10", "A10B9")  --&gt;  -1
      </pre>
      Any objects passed in will be converted to a string using the
      {@link Object#toString()} method, excepting that a check for null will be
      made first and any null values will be ordered before non-null values. <br>
      <br>
      Note that this does not support floating-point numbers (e.g. "3.14" and
      "0.314E1" and "314E-2" will not compare in the manner one may predict).
      This limitation is inherent since this comparator is intended to support
      file names and table keys, the roots of which don't include a dot (".").
   */
  public static final Comparator<Object> NAME_COMPARATOR
    = new Comparator<Object>() {
      @Override
      public int compare (Object _a, Object _b) {
        if (_a == _b  ) return  0; // Quick return here (identical objects)
        if (_a == null) return -1; // null comes first
        if (_b == null) return +1; // null comes first

        String a = _a.toString();
        String b = _b.toString();

        if (a.equals(b)) return 0;   // Quick return here (identical strings)

        int lenA;
        int lenB;
        while (((lenA = a.length()) > 0) && ((lenB = b.length()) > 0)) {
          // COUNT NUMBER OF NON-DIGITS
          int countA = 0;
          int countB = 0;
          while ((countA < lenA) && !isDigit09(a.charAt(countA))) countA++;
          while ((countB < lenB) && !isDigit09(b.charAt(countB))) countB++;

          // CHECK (AND REMOVE) NON-DIGIT PREFIX
          if ((countA > 0) || (countB > 0)) {
            String strA = a.substring(0,countA);
            String strB = b.substring(0,countB);
            int    comp = strA.compareTo(strB);

            if (comp != 0) return comp;

            // "ABC123XYZ" -> "123XYZ"
            a = a.substring(countA);  lenA = a.length();
            b = b.substring(countB);  lenB = b.length();
          }

          // COUNT NUMBER OF DIGITS
          countA = 0;
          countB = 0;
          while ((countA < lenA) && isDigit09(a.charAt(countA))) countA++;
          while ((countB < lenB) && isDigit09(b.charAt(countB))) countB++;

          // CHECK (AND REMOVE) DIGIT PREFIX
          if ((countA > 0) || (countB > 0)) {
            if (countA == 0) return -1; // e.g. "ABC" < "ABC0"
            if (countB == 0) return +1; // e.g. "ABC0" > "ABC"

            int intA = Integer.parseInt(a.substring(0,countA));
            int intB = Integer.parseInt(b.substring(0,countB));

            if (intA != intB) return Integer.compare(intA, intB);

            // "123XYZ" -> "XYZ"
            a = a.substring(countA);
            b = b.substring(countB);
          }
        }
        return a.compareTo(b);
      }
    };

  /** A {@link Comparator} to use for the keys of a set of {@link Map.Entry}
      objects. This function simply checks for null values and then calls
      <code>NAME_COMPARATOR.compare(a.getKey(), b.getKey())</code>.
   */
  static final Comparator<Map.Entry<?,?>> KEY_COMPARATOR
    = new Comparator<Map.Entry<?,?>>() {
      @Override
      public int compare (Map.Entry<?,?> a, Map.Entry<?,?> b) {
        if (a == b   ) return  0;
        if (a == null) return -1;
        if (b == null) return +1;
        return NAME_COMPARATOR.compare(a.getKey(), b.getKey());
      }
    };

  /** Return the index to the closing position that is opened at begPos. The fences is a two
      character String that specifies the open and close characters.  The character at begPos
      must be the first character at fences. <br>
      <br>
      Examples:
      <pre>
        getClosingFensePos("test {str{foo}}", "{}", 5)  <i>--&gt; 14</i>
        getClosingFensePos("test {str{foo}}", "{}", 9)  <i>--&gt; 13</i>
      </pre>
      Since NeXtMidas 3.3.2 it is possible that both opening and closing fences
      are the same (e.g. "''") in which case the next instance is returned (note
      that situations like this do not permit nesting). <br>
      <br>
      <i>Note: The use of "fense" rather than "fence" in the function name is
      the result of a typo in the initial version of this function back in the
      pre-1.8.0 days.</i>
      @param str    The string to find the closing position of the fence in.
      @param fences A <b>two element</b> string containing the open and close fences (e.g. <code>"{}"</code>).
      @param begPos The position of the open fence to find the matching close fence for.
      @return The ending position within the string to the closing element. If the string is
              <code>null</code> or smaller than <code>begPos</code> then <code>-1</code> will be returned.
      @throws IllegalArgumentException If <code>fences</code> is not a two character string.
   */
  public static int getClosingFensePos (String str, String fences, int begPos) throws IllegalArgumentException {
    if ((fences == null) || (fences.length() != 2)) {
      throw new IllegalArgumentException("StringUtil.getClosingFensePos(...) expects a two character "
                                       + "string for parameter fences, given \""+ fences + "\".");
    }

    if (str == null || str.length() < begPos + 1 ) {
      return -1;
    }
    char openChar  = fences.charAt(0); // opens a scope
    char closeChar = fences.charAt(1); // closes a scope

    if (openChar == closeChar) {
      // Since NeXtMidas 3.3.2 - Special case for situations where the begin and
      // end are the same, namely strings (fences='""') -- avoids infinite loop
      // in user code as it would otherwise return begPos.
      return str.indexOf(closeChar, begPos+1);
    }
    else {
      // Start at the character immediately following the begin position.
      // Examine every character for a fence character.  Bail out of this loop
      // when we've gotten to the end of the string or our level gets to zero.
      int pos = begPos + 1;
      for (int level = 1; level > 0 && pos < str.length(); pos++) {
        if (str.charAt(pos) == openChar ) level++;
        if (str.charAt(pos) == closeChar) level--;
        if (level == 0) return pos;
      }
      return -1; // not found
    }
  }

  /** Return the index to the opening position that is opened at endPos.  The
      fences is a two character String that specifies the open and close
      characters.  The character at endPos must be the first character at
      fences. <br>
      <br>
      Examples:
      <pre>
        getOpeningFensePos("test {str{foo}}", "{}", 14)  <i>--&gt; 5</i>
        getOpeningFensePos("test {str{foo}}", "{}", 13)  <i>--&gt; 9</i>
      </pre>
      Since NeXtMidas 3.3.2 it is possible that both opening and closing fences
      are the same (e.g. "''") in which case the next instance is returned (note
      that situations like this do not permit nesting). <br>
      <br>
      <i>Note: The use of "fense" rather than "fence" in the function name is
      the result of a typo in the initial version of this function back in the
      pre-1.8.0 days.</i>
      @param str    The string to find the opening position of the fence in.
      @param fences A <b>two element</b> string containing the open and close fences (e.g. <code>"{}"</code>).
      @param endPos The position of the close fence to find the matching open fence for.
      @return The starting position within the string to the opening element. If the string is
              <code>null</code> or smaller than <code>begPos</code> then <code>-1</code> will be returned.
      @throws IllegalArgumentException If <code>fences</code> is not a two character string.
   */
  public static int getOpeningFensePos (String str, String fences, int endPos) throws IllegalArgumentException {
    if ((fences == null) || (fences.length() != 2)) {
      throw new IllegalArgumentException("StringUtil.getOpeningFensePos(...) expects a two character "
                                       + "string for parameter fences, given \""+ fences + "\".");
    }

    if (str == null || endPos < 1) {
      return -1;
    }
    char openChar  = fences.charAt(0); // opens a scope
    char closeChar = fences.charAt(1); // closes a scope

    if (openChar == closeChar) {
      // Since NeXtMidas 3.3.2 - Special case for situations where the begin and
      // end are the same, namely strings (fences='""') -- avoids infinite loop
      // in user code as it would otherwise return endPos.
      return str.lastIndexOf(openChar, endPos-1);
    }
    else {
      // Start at the character immediately before the end position.
      // Examine every character for a fence character.  Bail out of this loop
      // when we've gotten to the start of the string or our level gets to zero.
      int pos = endPos - 1;
      for (int level = 1; level > 0 && pos >= 0; pos--) {
        if (str.charAt(pos) == openChar ) level--;
        if (str.charAt(pos) == closeChar) level++;
        if (level == 0) return pos;
      }
      return -1; // not found
    }
  }

  /** Returns the result of joining the stringified elements of the array using
      "," as delimiter. <br>
      <br>
      This is the same as: <code>toString(list,",&nbsp;")</code>

      @param list  The array of elements to join.

      @return The result of performing the join on the string, or
              <code>null</code> if <code>list</code> is <code>null</code> and
              <code>""</code> if <code>list</code> has a length of zero.

      @see #join(Object[],String)
      @see #toColumns(Object[],int)
   */
  public static String join (Object[] list) {
    return join(list, ", ");
  }

  /** Returns the result of joining the stringified elements of the array with
      the given delimiter. <br>
      <br>
      Examples:
      <pre>
        join(new String[] {"a", "b", "c"}, ",")   <i>--&gt; "a,b,c"</i>
        join(new String[] {"a", "b", "c"}, " ")   <i>--&gt; "a b c"</i>
        join(new String[] {"a", "b", "c"}, ", ")  <i>--&gt; "a, b, c"</i>
      </pre>
      Each element in <code>list</code> will be converted to a string by using
      it's <code>.toString()</code> method.

      @param list  The array of elements to join.
      @param delim The string to use between elements of the resulting string.

      @return The result of performing the join on the string, or
              <code>null</code> if <code>list</code> is <code>null</code> and
              <code>""</code> if <code>list</code> has a length of zero.

      @see Object#toString()
      @see #toColumns(Object[],int)
   */
  public static String join (Object[] list, String delim) {
    if ( list == null ) {
      return null;
    } else if (list.length == 0) {
      return "";
    } else {
      StringBuilder sb = new StringBuilder(200);
      for (int ii = 0; ii < list.length - 1; ii++) { // skip last item (to not insert trailing delimiter)
        sb.append(list[ii]);
        sb.append(delim);
      }
      sb.append(list [ list.length - 1 ] ); // add last item
      return sb.toString();
    }
  }

  /** This will convert the given elements into a columnar table. This is used
   *  by methods that wish to output a number of elements to the screen without
   *  using an inordinate amount of vertical screen space. <br>
   *  <br>
   *  This method will simply convert all of the objects to strings and then
   *  call the {@link #toColumns(String[],int)} method.
   *  @param objArray The list of strings to put into columns.
   *  @param maxWidth The maximum width to spread the elements across (this is
   *                  frequently <code>80</code> to correspond to the standard
   *                  console width).
   *  @return The columnar tabel with the elements.
   *  @see #toColumns(String[],int)
   */
  public static String toColumns (Object[] objArray, int maxWidth) {
    if (objArray == null) { objArray = EMPTY_STRING_ARRAY; } // makes sure obj is NOT null

    String[] strs = new String[objArray.length];

    for (int ii = 0; ii < strs.length; ii++) {
      strs[ii] = "" + objArray[ii]; /* Don't use objArray[i].toString() - objArray[i] may be null */
    }

    return toColumns(strs, maxWidth);
  }

  /** This will convert the given elements into a columnar table. This is used
      by methods that wish to output a number of elements to the screen without
      using an inordinate amount of vertical screen space. <br>
      <br>
      This is identical to
      <code>toColumns(obj,maxWidth,"&nbsp;&nbsp;","\n")</code>.

      @param strArray The list of strings to put into columns.
      @param maxWidth The maximum width to spread the elements across (this is
                      frequently <code>80</code> to correspond to the standard
                      console width).

      @return The objects displayed in a columnar format.

      @see #toColumns(String[],int,String,String)
   */
  public static String toColumns (String[] strArray, int maxWidth) {
    return toColumns(strArray, maxWidth, "  ", "\n");
  }

  /** This will convert the given elements into a columnar table. This is used
      by methods that wish to output a number of elements to the screen without
      using an inordinate amount of vertical screen space. <br>
      <br>
      Typical Usage:
      <pre>
         String[] values;

         ...

         System.out.println(toColumns(values, 80, "  ", "\n"));
      </pre>

      @param strArray The list of strings to put into columns.
      @param maxWidth The maximum width to spread the elements across (this is
                      frequently <code>80</code> to correspond to the standard
                      console width).
      @param spacer   The spacer between columns
                      (usually <code>"&nbsp;&nbsp;"</code>).
      @param eol      The End Of Line character (usually <code>"\n"</code>).

      @return The objects displayed in a columnar format.
   */
  public static String toColumns (String[] strArray, int maxWidth,
                                  String spacer, String eol) {
    /* Some sanity checks. */
    if (strArray == null) { strArray = EMPTY_STRING_ARRAY; }

    /* Find the maximum element length. */
    int maxEleLength = 0;
    for (String str : strArray) {
      maxEleLength = Math.max(maxEleLength, str.length());
    }

    /* Find the number of columns possible. */
    int columns = (maxWidth / (maxEleLength + spacer.length()));

    /* Make the new string */
    StringBuilder output = new StringBuilder(maxWidth);

    int index = 0;
    int col = 0;

    while (index < strArray.length) {
      if (col > columns) {
        output.append(eol);
        col = 0;
      } else {
        if (col != 0) {
          output.append(spacer);
        }

        if (col == columns) {
          output.append(strArray[index]); /* Don't pad last column. */
        } else {
          output.append(padRight(strArray[index], maxEleLength));
        }
        col++;
        index++;
      }
    }
    return output.toString();
  }

  /** Trims off the ends of a string. This will remove all text before the first
      occurrence of <code>start</code> and after the first occurrence of
      <code>end</code>. Additionally the delimiters <code>start</code> and
      <code>end</code> will be removed.<br>
      <br>
      If <code>start</code> does not exist, only the text after <code>end</code>
      will be removed. If <code>end</code> does not exist, only the text before
      <code>start</code> will be removed. If neither <code>start</code>  nor
      <code>end</code> exist, the complete string is returned.<br>
      <br>
      Examples:
      <pre>
        trim("Bananas $0.49/lb", "$", "/")  --&gt; "0.49"
        trim("Bananas $0.49/lb", "#", "$")  --&gt; "Bananas "
        trim("Bananas $0.49/lb", " ", "#")  --&gt; "$0.49/lb"
      </pre>

      @param string The string to trim.
      @param start  The beginning of the portion of the string to keep.
      @param end    The ending of the portion of the string to keep.

      @return The string with the ends trimmed off.
   */
  public static String trim (String string, String start, String end) {
    int i;
    int i1=0;
    int i2=string.length();

    if (start.length()>0) {
      i = string.indexOf(start);
      if (i>=0) i1=i+start.length();
    }
    if (end.length()>0) {
      i = string.indexOf(end,i1);
      if (i>=0) i2=i;
    }
    return string.substring(i1,i2);
  }

  /** Trims input object if it is a StringBuffer, StringBuilder, or String and
      and return Object of same type as input object back, otherwise
      just return input object untouched.
      @param obj Object to trim (if it is String, StringBuffer, or StringBuilder)
      @return trimmed object
      @since NeXtMidas 2.7.1
   */
  public static Object trimIfString (Object obj) {
    if (obj instanceof StringBuffer) {
      String str     = obj.toString();
      String trimmed = str.trim();
      if (!str.equals(trimmed)) { // only need to create new StringBuffer if trimmed string is different
        obj = new StringBuffer(trimmed);
      }
    }
    else if (obj instanceof StringBuilder) {
      String str     = obj.toString();
      String trimmed = str.trim();
      if (!str.equals(trimmed)) { // only need to create new StringBuilder if trimmed string is different
        obj = new StringBuilder(trimmed);
      }
    }
    else if (obj instanceof String) {
      obj = ((String)obj).trim();
    }
    return obj;
  }

  /** Remove leading whitespace (left trim) from a String.
   *  @param str The string to remove leading whitespace from.
   *  @return The string without leading whitespace.
   *  @since NeXtMidas 2.7.0
   */
  public static String ltrim (String str) {
    return LTRIM_REGEX.matcher(str).replaceAll("");
  }

  /** Remove trailing whitespace (right trim) from a String.
   *  @param str The string to remove trailing whitespace from.
   *  @return The string without trailing whitespace.
   *  @since NeXtMidas 2.7.0
   */
  public static String rtrim (String str) {
    return RTRIM_REGEX.matcher(str).replaceAll("");
  }

  /** Get the "inclusive" substring between imin and imax of string. A negative index may be
      used to indicate an index relative to the end of the String. <br>
      <br>
      Examples:
      <pre>
        substring("The quick brown fox", 0,   8)   --&gt; "The quick"
        substring("The quick brown fox", 4,   8)   --&gt; "quick"
        substring("The quick brown fox", 4,  50)   --&gt; "quick brown fox"
        substring("The quick brown fox", 4,  -5)   --&gt; "quick brown"
        substring("The quick brown fox", 0, -50)   --&gt; "T"
        substring("The quick brown fox", 0,   0)   --&gt; "T"
        substring("The quick brown fox",-1,  -1)   --&gt; "x"
      </pre>
      <br>
      <br>
      The {@link String#substring(int)} and {@link String#substring(int,int)}
      methods should be used if relative indexing and extra limit checking is
      not required.
      <br>
      This method differs from {@link #substring(String, int, int)} in how negative
      numbers are treated.<br>

      @param inString  The input string.
      @param imin      The index of start of string to extract (inclusive).
      @param imax      The index of end of string to extract (inclusive).

      @return The appropriate substring.

      @see String#substring(int)
      @see String#substring(int,int)
      @since NeXtMidas 3.1.2
  */
  public static String between (String inString, int imin, int imax) {
    /* negative numbers get subtracted from the end, so -1 is the last character */
    if (imin<0) { imin = inString.length() + imin; }
    if (imax<0) { imax = inString.length() + imax; }

    if (imax < imin) {
      int tmp = imin;
      imin = imax;
      imax = tmp;
    }

    imax++;  // make it inclusive

    /* keep the values in range */
    if (imin<0) imin=0;
    if (imin>inString.length()) imin = inString.length();
    if (imax<0) imax=0;
    if (imax>inString.length()) imax = inString.length();

    return inString.substring(imin, imax);
  }

  /** Get the "inclusive" substring between imin and imax of string. A negative index may be
      used to indicate a relative maximum index. <br>
      <br>
      Examples:
      <pre>
        substring("The quick brown fox", 0,   8)   --&gt; "The quick"
        substring("The quick brown fox", 4,   8)   --&gt; "quick"
        substring("The quick brown fox", 4,  50)   --&gt; "quick brown fox"
        substring("The quick brown fox", 4,  -5)   --&gt; "quick"
        substring("The quick brown fox", 0, -50)   --&gt; "The quick brown fox"
      </pre>
      <br>
      <br>
      The {@link String#substring(int)} and {@link String#substring(int,int)}
      methods should be used if relative indexing and extra limit checking is
      not required.

      @param inString  The input string.
      @param imin      The index of start of string to extract. Should be
                       non-negative. (inclusive)
      @param imax      The index (if non-negative) of end (inclusive) of string to extract
                       or the number of characters to extract (if negative).

      @return The appropriate substring.

      @see String#substring(int)
      @see String#substring(int,int)
   */
  public static String substring (String inString, int imin, int imax) {
    String outString = null;
    int len = inString.length();

    if (imin > imax) {
      int temp = imax;
      imax = imin;
      imin = temp;
    }

    if (imin < 0) {
      // Reference mode
      if (imax > len) imax = len;
      else if (imax < len) imax++;
      imin = Math.max (0, imax+imin-1);  // imin is negative
    } else if (imin >= len) {
      // Both indeces out of range
    } else {
      // min Index is valid. If the MAX index is > length
      // of input string we correct for BETWEEN function
      imax = Math.min (len,imax+1); // imax+i to make it inclusive
    }

    try {
      outString = inString.substring (imin, imax);
    } catch (Exception StringIndexOutOfBoundsException) {
      // Output string unchanged
    }
    return outString;
  }

  /** Strips the quotes off of a string. This will remove the first and
      last characters of the string if <u>both</u> are quotation marks
      (<code>"\""</code>).<br>
      <br>
      Examples:
      <pre>
        stripQuotes(null)         --&gt; null
        stripQuotes("abc")        --&gt; "abc"
        stripQuotes("\"abc\"")    --&gt; "abc"
        stripQuotes("\"abc")      --&gt; "\"abc"
        stripQuotes(" \"abc\" ")  --&gt; " \"abc\" "
      </pre>

      @param string The string to remove the quotes from.

      @return The string after removing the quotes.
   */
  public static String stripQuotes (String string) {
    if (string == null) return string;
    int ls =  string.length();
    if (ls==0 || string.charAt(0) != QUOTE) return string;
    if (string.charAt(ls-1) != QUOTE) return string.substring(1,ls);
    return string.substring(1,ls-1);
  }

  /** Return a new string of padded out to a pre-determined length by adding
      characters to the left. <br>
      <br>
      This is identical to: <code>padLeft(str,newlen,' ')</code>

      @param str     The string to pad.
      @param newlen  The length to pad the string out to.

      @return The string padded with spaces on the left to match the specified length.

      @see #padLeft(String,int,char)
      @see #padRight(String,int,char)
      @see #padBoth(String,int,char)
   */
  public static String padLeft (String str, int newlen) {
    return padLeft(str, newlen, ' ');
  }

  /** Return a new string of padded out to a pre-determined length by adding
      characters to the left. <br>
      <br>
      Examples:
      <pre>
        padLeft("cat", 5, '.')  <i>--&gt; "..cat"</i>
      </pre>

      @param str     The string to pad.
      @param newlen  The length to pad the string out to.
      @param padChar The character to pad the string with.

      @return The string padded on the left to match the specified length.

      @see #padRight(String,int,char)
      @see #padBoth(String,int,char)
   */
  public static String padLeft (String str, int newlen, char padChar) {
    StringBuilder buffer = new StringBuilder(newlen);

    for (int ii = str.length(); ii < newlen; ii++) {
      buffer.append(padChar);
    }

    return buffer.append(str).toString();
  }

  /** Return a new string of padded out to a pre-determined length by adding
      characters to the right. <br>
      <br>
      This is identical to: <code>padRight(str,newlen,' ')</code>

      @param str     The string to pad.
      @param newlen  The length to pad the string out to.

      @return The string padded with spaces on the right to match the specified length.

      @see #padLeft(String,int,char)
      @see #padRight(String,int,char)
      @see #padBoth(String,int,char)
   */
  public static String padRight (String str, int newlen) {
    return padRight(str, newlen, ' ');
  }

  /** Return a new string of padded out to a pre-determined length by adding
      characters to the right. <br>
      <br>
      Examples:
      <pre>
        padRight("cat", 5, '.')  <i>--&gt; "cat.."</i>
      </pre>

      @param str     The string to pad.
      @param newlen  The length to pad the string out to (negative will be ignored)
      @param padChar The character to pad the string with.

      @return The string padded on the right to match the specified length.

      @see #padLeft(String,int,char)
      @see #padBoth(String,int,char)
   */
  public static String padRight (String str, int newlen, char padChar) {
    int len = str.length();
    if (newlen <= len) return str; // no padding needed
    return padRight(new StringBuilder(newlen), str, newlen, padChar).toString();
//  TODO: remove below after 3.9+
//    StringBuilder buffer = ;
//    buffer.append(str);
//    for (int ii = len; ii < newlen; ii++) {
//      buffer.append(padChar);
//    }
//
//    return buffer.toString();
  }

  /** Pad out to a pre-determined length by adding characters to the right of input string.
      If padStr is already greater equal to padLen, no padChar will be appended.
      @param destSB  the destination StringBuilder to append padStr to (cannot be null)
      @param str     the string to pad (cannot be null)
      @param padLen  the length to pad the string out to if str.length is shorter
      @param padChar the character to pad the string with
      @return the input/destination StringBuilder with padStr appended (and padded to padLen with padChar)
      @since 3.5.2
   */
  public static StringBuilder padRight (StringBuilder destSB, String str, int padLen, char padChar) {
    destSB.append(str);
    for (int ii = str.length(); ii < padLen; ii++) {
      destSB.append(padChar);
    }
    return destSB;
  }

  /** Center a string on a line of specified length. If the string is already
      longer then the specified width, then the string will just be returned.
      <br>
      <br>
      In the event that an odd number of characters needs to be added to the
      string to pad it out, the "extra" character will be added to the right
      of the given string.added out to a pre-determined length by adding
      characters to the right. <br>
      <br>
      Examples:
      <pre>
        padBoth("cat", 5, '.')  <i>--&gt; ".cat."</i>
      </pre>

      @param str     The string to center.
      @param width   The width of the line.
      @param padChar The character to pad the string with.

      @return The string centered on the line.

      @see #padLeft(String,int,char)
      @see #padRight(String,int,char)
   */
  public static String padBoth (String str, int width, char padChar) {
    int addLeft  = (width - str.length()) / 2;
    int addRight = width - str.length() - addLeft;

    StringBuilder buffer = new StringBuilder(width);

    for (int ii = 0; ii < addLeft; ii++) {
      buffer.append(padChar);
    }

    buffer.append(str);

    for (int ii = 0; ii < addRight; ii++) {
      buffer.append(padChar);
    }

    return buffer.toString();
  }

  /** Center a string on a line of specified length. If the string is already
      longer then the specified width, then the string will just be returned.
      <br>
      <br>
      This is identical to: <code>padBoth(str,newlen,' ')</code>

      @param str     The string to pad.
      @param newlen  The length to pad the string out to.

      @return The string padded with spaces on the right to match the specified
              length.

      @see #padLeft(String,int,char)
      @see #padRight(String,int,char)
      @see #padBoth(String,int,char)
   */
  public static String padBoth (String str, int newlen) {
    return padBoth(str, newlen, ' ');
  }


  /** Determines if all of the elements of the string are whitespace.

      @param str The string to test.

      @return <code>true</code> if the string contains only whitespace elements
              and <code>false</code> otherwise.

      @see Character#isWhitespace(char)
   */
  public static boolean isWhitespace (String str) {
    return (str.trim().length() == 0);
  }

  /** Return the given string converted into valid HTML. This will convert all
      text that matches an HTML control character to it's HTML equivalent, the
      following is a summary of the conversions performed by this method:
      <table border="1">
        <caption style="visibility:hidden">""</caption>
        <tr>
          <th>Plain Text</th>
          <th>HTML Version</th>
        </tr>
        <tr>
          <td><code>&nbsp;</code></td>
          <td><code>&amp;amp;</code></td>
        </tr>
        <tr>
          <td><code>&lt;</code></td>
          <td><code>&amp;lt;</code></td>
        </tr>
        <tr>
          <td><code>&gt;</code></td>
          <td><code>&amp;gt;</code></td>
        </tr>
        <tr>
          <td><code>&quot;</code></td>
          <td><code>&amp;quot;</code></td>
        </tr>
      </table>

      @param text The string to convert to HTML.
      @return A HTML version of the string.
      @deprecated Since NeXtMidas 2.7.3: Use {@link #toHTML(CharSequence)}.
   */
  @Deprecated public static String getHTMLFor (String text) {
    String result = text;
    result = AMP_REGEX.matcher(result).replaceAll("&amp;");
    result = LT_REGEX.matcher(result).replaceAll("&lt;");
    result = GT_REGEX.matcher(result).replaceAll("&gt;");
    result = QUOTE_REGEX.matcher(result).replaceAll("&quot;");
    return result;
  }

  /** Indicates if a given string is equal to either <code>null</code> or the
      String <code>"NULL"</code>. Case is ignored when performing this test.
      <br>
      <br>
      Note that this method does not consider the empty string (<code>""</code>)
      to be a null string.

      @param str The string.
      @return <code>true</code> if the string is <code>null</code> or is equal
              to (with ignoring case) <code>"NULL"</code>; <code>false</code> otherwise.
      @see #convertNullsToEmpty(String)
      @see #isNullOrEmpty(Object)
   */
  public static boolean isNull (String str) {
    return (str == null || str.equalsIgnoreCase("NULL"));
  }

  /** Check if specified String/CharSequence is empty (i.e. if it is null or it's length is zero).
      @param seq String/CharSequence to check if it is logically empty (null or length() is zero)
      @return true if input string is empty (null or length() == 0), otherwise false
      @since NeXtMidas 3.0.1
   */
  public static boolean isEmpty (CharSequence seq) {
    return ((seq == null) || (seq.length() == 0));
  }

  /** Check if specified object is null or it's toString() value is "null", "NULL", or "" (empty string).
      @param value object to check
      @return true if value is null or it's toString() passes {@link #isEmpty(CharSequence)} or {@link #isNull(String)}.
      @since NeXtMidas 3.3.1
   */
  @InternalUseOnly
  public static boolean isNullOrEmpty (Object value) {
    if (value == null) return true;
//  return ("".equals(value) || "null".equals(value) || "NULL".equals(value)); // <-- TODO: limit to just Strings?
    String str = value.toString();
    return isEmpty(str) || isNull(str);
  }

  /** Filters out null strings by converting them to the empty string.

      @param str The string to convert to an empty string if it is a null string.
      @return If the given string is a null string (as determined by
              {@link #isNull(String)}) the empty string (<code>""</code>) is
              returned, otherwise the original string is returned.
      @see #isNull(String)
   */
  public static String convertNullsToEmpty (String str) {
    return (isNull(str)? "" : str);
  }

  /** Filters out null objects by converting them to the empty string. A null
      object is any object that is <code>null</code> or for which
      <code>isNull(obj.toString())</code> returns <code>true</code>.

      @param obj The string to convert to an empty string if it is a null object.

      @return If the given string is a null string (as determined by
              {@link #isNull(String)}) the empty string (<code>""</code>) is
              returned, otherwise the original string is returned.

      @see #isNull(String)
      @see #convertNullsToEmpty(String)
   */
  public static Object convertNullsToEmpty (Object obj) {
    return (((obj == null) || (isNull(obj.toString())))? "": obj);
  }

  /** Get the value from a string containing key-value pairs. <br>
      <br>
      This is the same as <code>getKeyValue(inString,tag,null)</code>.

      @param inString The input string
      @param tag      The name of the tag

      @return Everything from the matching tag plus delimiter to the first
              whitespace character or <code>null</code> if tag is not found.

      @see #getKeyValue(String,String,String)
   */
  public static String getKeyValue (String inString, String tag) {
    return getKeyValue(inString,tag,null);
  }

  /** Get the value from a string containing key-value pairs. A key-value pair
      is a pairing such as <code>TAG1=VAL1 TAG2=VAL2 ...</code>. <br>
      <br>
      Examples:
      <pre>
        getKeyValue("name=smith age=42", "name",   "=")   --&gt; "smith"
        getKeyValue("name=smith age=42", "height", "=")   --&gt; null
        getKeyValue("name:smith age:42", "age",    ":")   --&gt; "42"
      </pre>

      @param inString The input string
      @param tag      The name of the tag
      @param delim    The delimiter to use. If this is <code>null</code> or
                      <code>""</code> the equal sign (<code>=</code>) will be used.

      @return Everything from the matching tag plus delimiter to the first
              whitespace character or <code>null</code> if tag is not found.
   */
  public static String getKeyValue (String inString, String tag, String delim) {
    String value = null;
    if (delim == null || delim.length() <= 0) delim = "=";
    int iTag = inString.indexOf(tag+delim);
    if (iTag >= 0) {
      String subStr = inString.substring(iTag);
      int iDelim = subStr.indexOf(delim);
      subStr = subStr.substring(iDelim + delim.length() - 1);
      char[] charArray = new char [ subStr.length() ];
      subStr.getChars(0,subStr.length(),charArray,0);
      int iWhiteSpace = subStr.length();
      for (int ii=0; ii < charArray.length; ii++) {
        if (Character.isWhitespace(charArray[ii])) {
          iWhiteSpace = ii;
          break;
        }
      }
      value = subStr.substring(1,iWhiteSpace);
    }
   return value;
  }

  /** This will convert any plain text into a regular expression that can be
      used with the Java regular expression-based methods. <br>
      <br>
      The following characters have special meaning to regular expressions:
      "<code>\()[]{}.^$?+|:&amp;*=-!&lt;&gt;</code>". This method will make sure
      that they are properly escaped.

      @param text The plain text to convert to a regular expression.

      @return The regular expression that matches the supplied text.

      @see #split(String,String)
      @see #replaceAll(String,String,String)
      @see #replaceFirst(String,String,String)
   */
  public static String textToRegex (String text) {
    StringBuilder regex = new StringBuilder(text.length() + 5);

    for (int ii = 0; ii < text.length(); ii++) {
      switch(text.charAt(ii)) {
        case '\\':
          regex.append("\\\\");
          break;

        case '(':  case ')':  case '[':  case ']':  case '{':  case '}':
        case '^':
          regex.append('\\');
          regex.append(text.charAt(ii));
          break;

        case '.':  case '$':  case '+':  case '|':  case '&':  case ':':
        case '=':  case '-':  case '!':  case '<':  case '>':  case '?':
        case '*':
          regex.append('[');
          regex.append(text.charAt(ii));
          regex.append(']');
          break;

        default:
          regex.append(text.charAt(ii));
          break;
      }
    }
    return regex.toString();
  }

  /** This will convert a "wildcard string" into a regular expression that can
      be used with the Java regular expression-based methods. <br>
      <br>
      This method is identical to the {@link #textToRegex(String)} method,
      except that it converts the wildcard characters to their regular
      expression counterparts.
      <table border="1">
        <caption style="visibility:hidden">""</caption>
        <tr>
          <th>Wildcard</th>
          <th>Meaning</th>
          <th>Regular Expression</th>
        </tr>
        <tr>
          <td style="text-align:center"><code>*</code></td>
          <td style="text-align:center">Matches any character<br>
                             zero or more times.</td>
          <td style="text-align:center"><code>.*</code></td>
        </tr>
        <tr>
          <td style="text-align:center"><code>?</code></td>
          <td style="text-align:center">Matches any character<br>
                             exactly one time.</td>
          <td style="text-align:center"><code>.</code></td>
        </tr>
      </table>

      @param text The plain text to convert to a regular expression.

      @return The regular expression that matches the supplied text.

      @see #split(String,String)
      @see #replaceAll(String,String,String)
      @see #replaceFirst(String,String,String)
   */
  public static String wildcardsToRegex (String text) {
    Pattern star  = Pattern.compile("\\[[*]\\]");
    Pattern quest = Pattern.compile("\\[[?]\\]");

    String regex = textToRegex(text);

    regex = star.matcher(regex).replaceAll(".*");
    regex = quest.matcher(regex).replaceAll(".");

    return regex;
  }

  /** This will convert any plain text into a replacement string that can be
      used with the Java regular expression-based methods. <br>
      <br>
      The following characters have special meaning to replacement strings:
      "<code>\$</code>". This method will make sure that they are properly
      escaped.

      @param text The plain text to convert to a replacement string.

      @return The replacement string that matches the supplied text.

      @see #split(String,String)
      @see #replaceAll(String,String,String)
      @see #replaceFirst(String,String,String)
   */
  public static String textToReplacement (String text) {
    StringBuilder match = new StringBuilder(text.length() + 4);

    for (int ii = 0; ii < text.length(); ii++) {
      switch(text.charAt(ii)) {
        case '$':   match.append("\\$");          break;
        case '\\':  match.append("\\\\");         break;
        default:    match.append(text.charAt(ii)); break;
      }
    }

    return match.toString();
  }

  /** Within a string this will replace all occurrences of one substring with
      another substring. <br>
      <br>
      Examples:
      <pre>
        replaceAll("The small dog.", " ",   ","  )   --&gt; "The,small,dog."
        replaceAll("The small dog.", "dog", "cat")   --&gt; "The small cat."
        replaceAll("The small dog.", "elf", "cat")   --&gt; "The small dog."
      </pre><br>
      <br>
      The difference between this method and the
      {@link String#replaceAll(String,String)} method is that this takes in
      <b>plain-text</b> patterns and replacements whereas the Java method
      takes in a <b>regular expression</b> a specialized replacement. The
      {@link String#replaceAll(String,String)} method should be used in
      place of this method whenever possible.

      @param string      The to make replacements on.
      @param pattern     The plain-text pattern identifying what is to be
                         replaced.
      @param replacement The plain-text replacement for anything that matches
                         the pattern.

      @return A new version of <code>string</code> where all occurrences
              of <code><i>pattern</i></code> have been replaced with
              <code><i>replacement</i></code>.

      @see #replaceFirst(String,String,String)
      @see String#replaceAll(String,String)
      @see #textToRegex(String)
      @see #textToReplacement(String)
   */
  public static String replaceAll (String string, String pattern, String replacement) {
    return string.replaceAll(textToRegex(pattern), textToReplacement(replacement));
  }

  /** Within a string this will replace the first occurrence of one substring
      with another substring. <br>
      <br>
      Examples:
      <pre>
        replaceAll("The small dog.", " ",   ","  )   --&gt; "The,small dog."
        replaceAll("The small dog.", "dog", "cat")   --&gt; "The small cat."
        replaceAll("The small dog.", "elf", "cat")   --&gt; "The small dog."
      </pre><br>
      <br>
      The difference between this method and the
      {@link String#replaceFirst(String,String)} method is that this takes in
      <b>plain-text</b> patterns and replacements whereas the Java method
      takes in a <b>regular expression</b> a specialized replacement. The
      {@link String#replaceFirst(String,String)} method should be used in
      place of this method whenever possible.

      @param string      The to make replacements on.
      @param pattern     The plain-text pattern identifying what is to be
                         replaced.
      @param replacement The plain-text replacement for anything that matches
                         the pattern.

      @return A new version of <code>string</code> where the first occurrence
              of <code><i>pattern</i></code> has been replaced with
              <code><i>replacement</i></code>.

      @see #replaceAll(String,String,String)
      @see String#replaceFirst(String,String)
      @see #textToRegex(String)
      @see #textToReplacement(String)
   */
  public static String replaceFirst (String string, String pattern, String replacement) {
    return string.replaceFirst(textToRegex(pattern), textToReplacement(replacement));
  }

  /** Returns a string array which is the result of splitting the string using
      the specified delimiter. The return value will be an array of strings.
      If either the value or the delimiter is null, then the method will
      simply return null. <br>
      <br>
      Examples:
      <pre>
        split("The quick brown fox", " ")
                          --&gt; new String[] { "The", "quick", "brown", "fox" }

        split("The quick brown fox", "$")
                          --&gt; new String[] { "The quick brown fox" }

        split("The,quick,brown,fox", ",")
                          --&gt; new String[] { "The", "quick", "brown", "fox" }
      </pre>
      The difference between this method and the {@link String#split(String)}
      method are as follows:
      <ul>
        <li>
          This method takes in a <b>plain-text</b> delimiter to use and
          {@link String#split(String)} takes in a <b>regular expression</b>.
        </li>
        <li>
          If <code>val</code> is the empty string this method returns
          <code>new String[0]</code> and {@link String#split(String)}
          would return <code>new String[] {""}</code>.
        </li>
        <li>
          If <code>val</code> contains two (or more) delimiters that immediately
          follow each other {@link String#split(String)} will treat these
          delimiters as if there was only a single delimiter there while this
          method treats them all as unique delimiters separated by the empty
          string.
        </li>
      </ul>
      Examples of the difference between this method and the
      {@link String#split(String)} method:
      <pre>
        split('', 'S')                 --&gt; { }
        stringSplit('', 'S')           --&gt; { "" }

        split('USS', 'S')              --&gt; { "U", "" }
        stringSplit('USS', 'S')        --&gt; { "U" }

        split('SOS', 'S')              --&gt; { "", "O" }
        stringSplit('SOS', 'S')        --&gt; { "", "O" }

        split('SSO', 'S')              --&gt; { "", "", "O" }
        stringSplit('SSO', 'S')        --&gt; { "", "", "O" }

        split('SSS', 'S')              --&gt; { "", "", "" }
        stringSplit('SSS', 'S')        --&gt; { }

        split('SOO', 'S')              --&gt; { "", "OO" }
        stringSplit('SOO', 'S')        --&gt; { "", "OO" }

        split('OOS', 'S')              --&gt; { "OO" }
        stringSplit('OOS', 'S')        --&gt; { "OO" }

        split('OSO', 'S')              --&gt; { "O", "O" }
        stringSplit('OSO', 'S')        --&gt; { "O", "O" }


        Where stringSplit(str,delim) is short for str.split(textToRegex(delim)).
      </pre>
      In most cases it is best to use the {@link String#split(String)} method in
      place of this method.

      @param val   The string to split.
      @param delim The delimiter to split the string with.

      @return A new array of strings which is the result of the split, or
              <code>null</code> if either parameter is <code>null</code>.

      @see String#split(String)
      @see #cleanSplit(String,String)
      @see #textToRegex(String)
   */
  public static String[] split (String val, String delim) {
    if ( val == null || delim == null )
      return null;

    // Initialize a return list.
    ArrayList<String> list = new ArrayList<>(16);

    // Initialize an empty String
    StringBuilder tmpStr = new StringBuilder(val.length());

    // Iterate through every character in the array
    for (int ii = 0; ii < val.length(); ii++) {
        // If the character is not a delimiter char, append it to the
        // String, go to the next char.
      char vc = val.charAt(ii);
      if ( delim.indexOf (vc) == -1 ) {
        tmpStr.append(vc);
      }

      // If the character is a delimiter char, add the string to the
      // return list, and reset the empty string to a new string.
      else {
        list.add(tmpStr.toString());
        tmpStr.setLength(0);
      }
    } // end for loop

    // If the string is not empty, push it onto the list
    if (tmpStr.length() > 0) {
      list.add(tmpStr.toString());
    }

    // Now convert the list into an array of strings
    return list.toArray(EMPTY_STRING_ARRAY);
  }

  /** Returns a string array which is the result of splitting the string using
      the specified delimiter. The return value will be an array of strings.
      If either the value or the delimiter is null, then the method will
      simply return null. <br>
      <br>
      This method is identical to {@link #split(String,String)} <b>except</b>
      that it will remove any empty strings from the result. Examples of this
      difference include:
      <pre>
        split('', 'S')                 --&gt; { }
        cleanSplit('', 'S')            --&gt; { }

        split('USS', 'S')              --&gt; { "U", "" }
        cleanSplit('USS', 'S')         --&gt; { "U" }

        split('SOS', 'S')              --&gt; { "", "O" }
        cleanSplit('SOS', 'S')         --&gt; { "O" }

        split('SSS', 'S')              --&gt; { "", "", "" }
        cleanSplit('SSS', 'S')         --&gt; { }

        split('SOO', 'S')              --&gt; { "", "OO" }
        cleanSplit('SOO', 'S')         --&gt; { "OO" }

        split('OOS', 'S')              --&gt; { "OO" }
        cleanSplit('OOS', 'S')         --&gt; { "OO" }

        split('OSO', 'S')              --&gt; { "O", "O" }
        cleanSplit('OSO', 'S')         --&gt; { "O", "O" }
      </pre>

      @param val   The string to split.
      @param delim The delimiter to split the string with.

      @return A new array of strings which is the result of the split, or
              <code>null</code> if either parameter is <code>null</code>.

      @see #split(String,String)
   */
  public static String[] cleanSplit (String val, String delim) {
    String[] split = split(val, delim);

    if (split != null) {
      ArrayList<String> clean = new ArrayList<>(split.length);

      for (String str : split) {
        if (str.length() > 0) {
          clean.add(str);
        }
      }

      return clean.toArray(new String[clean.size()]);
    }
    else {
      return null;
    }
  }

  /** Returns a string array which is the result of splitting the input string using
      any combination of common Midas separators: comma (','), pipe (i.e. vertical bar, '|'),
      or whitespace (space,tab,etc.). The split spans across multiple consecutive
      delimiters.
      @param str The string to split
      @return A new array of strings which is the result of the split.
              If input string is null, an empty string array is returned.
      @see #cleanSplit(String)
      @see #cleanSplit(String,String)
      @see #split(String, String)
      @since NeXtMidas 3.1.2
   */
  public static String[] split (String str) {
    if (str == null) return EMPTY_STRING_ARRAY;
    String[] results = SEPATORS_REGEX.split(str);
    return results;
  }

  /** Returns a string array which is the result of splitting the input string using
      any combination of common Midas separators: comma (','), pipe (i.e. vertical bar, '|'),
      or whitespace (space,tab,etc.). The split spans across multiple consecutive
      delimiters.
      <br>
      This method is identical to {@link #split(String)} <b>except</b>
      that it will remove any empty strings from the result.
      @param str The string to split
      @return A new array of strings which is the result of the split.
              If input string is null, an empty string array is returned.
      @see #cleanSplit(String, String)
      @see #split(String)
      @see #split(String, String)
      @since NeXtMidas 3.1.2
   */
  public static String[] cleanSplit (String str) {
    if (str == null) return EMPTY_STRING_ARRAY;
    String[] results = split(str);
    ArrayList<String> cleanlist = new ArrayList<>(results.length);
    for (String s : results) {
      if ((s != null) && (s.length() > 0)) { // only include non-null and non-empty strings
        cleanlist.add(s);
      }
    }
    if (cleanlist.size() < results.length) { // some null or empty strings were cleaned out
      return cleanlist.toArray(EMPTY_STRING_ARRAY);
    }
    return results;
  }

  /** Gets the specified element of a parsed string based on its index. <br><br>
      This is identical to <code>parseString(string,clean)[index]</code>
      except that it will return <code>""</code> if the value of
      <code>index</code> is not within range.

      @param string   The string to parse.
      @param index    The index of the element to get.
      @param clean    Indicates that the string should be cleaned while parsing
                      (see {@link #cleanString(String)} for details).

      @return The element within the parsed string at the specified index or
              <code>""</code> if <code>index</code> is invalid.

      @see #cleanString(String)
      @see #parseString(String,boolean)
      @see #getParsedElementIndex(String,String,boolean)
      @see Parser
   */
  public static String getParsedElement (String string, int index, boolean clean) {
    Parser p = new Parser(string, clean);
    return p.get(index);
  }

  /** Gets the specified element of a parsed string based on its index. <br><br>
      This is identical to <code>parseString(string,clean)[index]</code>
      except that it will return <code>""</code> if the value of
      <code>index</code> is not within range.
      @param string   The string to parse.
      @param index    The index of the element to get.
      @param delim    The delimiter
      @param clean    Indicates that the string should be cleaned while parsing
                      (see #cleanString(String)} for details).
      @return The element within the parsed string at the specified index or
              <code>""</code> if <code>index</code> is invalid.
      @see #cleanString(String)
      @see #parseString(String,boolean)
      @see #getParsedElementIndex(String,String,boolean)
      @see Parser
   */
  public static String getParsedElement (String string, int index, char delim, boolean clean) {
    Parser p = new Parser(string, clean);
    p.setDelimiter(delim);
    return p.get(index);
  }

  /** Gets the index of the specified element of a parsed string. <br>
      <br>
      If <code>clean</code> is <code>true</code> then the elements in the string
      will be compared using a <i>case-insensitive</i> comparison, otherwise a
      <i>case-sensitive</i> comparison will be used.

      @param string   The string to parse.
      @param element  The element to find the index of.
      @param clean    Indicates that the string should be cleaned while parsing
                      (see #cleanString(String)} for details).

      @return The index of the specified element within the parsed string or
              <code>-1</code> if such does not exist.

      @see #cleanString(String)
      @see #parseString(String,boolean)
      @see #getParsedElement(String,int,boolean)
      @see Parser
   */
  public static int getParsedElementIndex (String string, String element, boolean clean) {
    Parser p = new Parser(string, clean);
    String itemToCheck = (clean? element.toUpperCase(): element);

    for (int ii=1; p.more(); ii++) {
      if (itemToCheck.equals(p.get(ii))) {
        return ii-1;
      }
    }

    return -1;
  }

  /** Parse string with default "natural" space or comma delimiter.
      See {@link #parseString(String,int,char,boolean)} for details.
      @param string   The string to parse.
      @param elements The maximum number of elements to get when parsing.
      @param clean    Indicates that the string should be cleaned while parsing
      @return An array of the first <i><code>elements</code></i> elements in the string.
  */
  public static String[] parseString (String string, int elements, boolean clean) {
    return (new Parser(string, clean)).getArray(elements);
  }

  /** Parses a string into its respective elements. Each element will be a single token
      (word/number/quoted-string) where all elements have been converted to uppercase unless they
      are within quotes. <br><br>
      The {@link #parseString(String,boolean)} method should be used when only all of elements are
      desired. <br><br>
      The {@link String#split(String,int)} method should be used when not parsing a Midas
      command/parameter string.
      @param string   The string to parse.
      @param elements The maximum number of elements to get when parsing.
      @param delim    Character to delimit string, use default if null.
      @param clean    Indicates that the string should be cleaned while parsing
                      (see {@link #cleanString(String)} for details).
      @return An array of the first <i><code>elements</code></i> elements in the string.
      @see #parseString(String,boolean)
      @see #cleanString(String)
      @see String#split(String,int)
      @see Parser
  */
  public static String[] parseString (String string, int elements, char delim, boolean clean) {
    Parser p = new Parser(string, clean);
    p.setDelimiter(delim);
    return p.getArray(elements);
  }

  /** Parses a string into its respective elements. Each element will be a
      single token (word/number/quoted-string) where all elements have been
      converted to uppercase unless they are within quotes. <br><br>
      The {@link #parseString(String,int,boolean)} method should be used when
      only a limited number of elements is desired. <br><br>
      The {@link String#split(String)} method should be used when not parsing
      a Midas command/parameter string.
      @param string   The string to parse.
      @param clean    Indicates that the string should be cleaned while parsing
                      (see #cleanString(String)} for details).
      @return An array of the all the elements in the string.
      @see #parseString(String,int,boolean)
      @see #cleanString(String)
      @see String#split(String)
      @see Parser
  */
  public static String[] parseString (String string, boolean clean) {
    Parser p = new Parser(string, clean);
    return p.getArray(0);
  }

  /** Returns a "cleaned" version of a string. Cleaning a string implies that:
      <ul>
        <li>All characters outside of quotes are uppercased.</li>
        <li>Any extra whitespace outside of quotes is compressed.</li>
        <li>All elements (or words) are delimited by a comma.</li>
      </ul>
      @param string The string to clean.
      @return A cleaned version of the string.
      @see Parser#clean()
      @see Parser
  */
  public static String cleanString (String string) {
    return (new Parser(string, true)).getAll();
  }

  /** Returns whether the string has any wildcard characters in it. For an
      explanation of the meaning of the wildcard characters see
      {@link #wildcardsToRegex(String)}.

      @param str The string to test to see if it has any wild cards.

      @return <code>true</code> if there are any wild cards in the string and
              <code>false</code> otherwise. (If the string is <code>null</code>
              then <code>false</code> will be returned.)

      @see #isWildcardMatch(String,String)
      @see #wildcardsToRegex(String)
   */
  public static boolean isWildcardString (String str) {
    String WILDCARD_CHARACTERS = "*?#";

    if (str != null) {
      for (int ii = 0; ii < WILDCARD_CHARACTERS.length(); ii++) {
        if (str.indexOf(WILDCARD_CHARACTERS.charAt(ii)) >= 0) {
          return true;
        }
      }
    }
    return false;
  }

  /** Return the result of performing a wildcard comparison between two strings.
      For an explanation of the meaning of the wildcard characters that may be
      used and see {@link #wildcardsToRegex(String)}.<br>
      <br>
      <br>
      For more advanced pattern matching, using regular expressions, see the
      {@link String#matches(String)} method.

      @param pattern The string containing wildcards.
      @param string  The string to compare to the wildcard string.

      @return <code>true</code> if the string matches the wildcard pattern,
              <code>false</code> otherwise. If either string is
              <code>null</code>, <code>false</code> is returned.

      @see #isWildcardString(String)
      @see #wildcardsToRegex(String)
   */
  public static boolean isWildcardMatch (String pattern, String string) {
    return string.matches(wildcardsToRegex(pattern));
  }

  /** Tests to see if a string represents a true value. This will test to see
      if the given string matches any of the truth values defined in
      {@link Args#trueStates}. If the string matches then this will return
      <code>true</code> and if it does not it will return <code>false</code>.
      <br>
      <br>
      Since {@link Args#trueStates} and {@link Args#falseStates} are not
      mutually exclusive the following expression is <b><u>NOT</u></b> true
      in the general case:
      <pre>
        isTrue(str) == !isFalse(str)
      </pre>
      Use <code>isTrue(str)</code> and <code>!isTrue(str)</code> when defining all
      non-true states to be false and use <code>isFalse(str)</code> and
      <code>!isFalse(str)</code> when defining all non-false states to be
      true.

      @param str The string to test.

      @return <code>true</code> if the string represents a true value and<br>
              <code>false</code> if the string does not represent a true value or is null.

      @see #isFalse(String)
      @see Args#trueStates
      @see Args#falseStates
   */
  public static boolean isTrue (String str) {
    return (str == null)? false: Args.trueStates.contains("|" + str.toUpperCase() + "|");
  }

  /** Tests to see if a string represents a false value. This will test to see
      if the given string matches any of the failure values defined in
      {@link Args#falseStates}. If the string matches then this will return
      <code>true</code> and if it does not it will return <code>false</code>.
      <br>
      <br>
      See {@link #isTrue(String)} for more details on how this function works
      and how it differs from {@link #isTrue(String)}.

      @param str The string to test.

      @return <code>true</code> if the string represents a false value and<br>
              <code>false</code> if the string does not represent a false value or is null.

      @see #isTrue(String)
      @see Args#trueStates
      @see Args#falseStates
   */
  public static boolean isFalse (String str) {
    return (str == null)? false: Args.falseStates.contains("|" + str.toUpperCase() + "|");
  }

  /** Tests to see if a string represents a numeric value. <br>
      <br>
      This is the same as using: <code>isFloat(str)||isHexInteger(str)</code>
      @param str The string to test.
      @return <code>true</code> if the string represents a false value and
              <code>false</code> if the string does not represent a false value.
      @see #isHexInteger(String)
      @see #isFloat(String)
      @since NeXtMidas 2.1.0
   */
  public static boolean isNumber (String str) {
    return isFloat(str)||isHexInteger(str);
  }

  /** Tests to see if a string represents an integer value. Integers may be
      positive or negative. Positive integers should not have a '+' or '-'
      sign while negative integers should start with a '-' sign.<br>
      <br>
      Examples:
      <pre>
        isInteger("")              --&gt; false
        isInteger("-")             --&gt; false
        isInteger(null)            --&gt; false
        isInteger("42")            --&gt; true
        isInteger("01")            --&gt; true
        isInteger("-7")            --&gt; true
        isInteger("+7")            --&gt; false
        isInteger("1024")          --&gt; true
        isInteger("ONE")           --&gt; false
        isInteger("1O")            --&gt; false (Ends with a capital 'o')
        isInteger("10")            --&gt; true  (Ends with a zero)
      </pre>
      This does not check to see that the integer value falls within the
      valid range for an <code>int</code>.
      @param str The string to test.
      @return <code>true</code> if the string represents a false value and<br>
              <code>false</code> if the string does not represent a false value.
      @see #isHexInteger(String)
      @see #isNumber(String)
      @since NeXtMidas 2.1.0
   */
  public static boolean isInteger (String str) {
    return matchesRegex(INTEGER_REGEX, str);
  }

  /** Tests to see if a string represents a hexadecimal value. Hexadecimal values
      must start with (<code>"0x"</code> or <code>"0x"</code>) and
      contain 1-N digits, each of which is a hex digit (0-9, a-f, A-F).<br>
      <br>
      Examples:
      <pre>
        isHexInteger("0x0040")        --&gt; true
        isHexInteger("0x1A")          --&gt; true
        isHexInteger("0X0")           --&gt; true
        isHexInteger("0xCAFEBABE")    --&gt; true
        isHexInteger("")              --&gt; false
        isHexInteger(null)            --&gt; false
        isHexInteger("0x1de")         --&gt; true
        isHexInteger("ONE")           --&gt; false
        isHexInteger("0X")            --&gt; false
        isHexInteger("27")            --&gt; false
        isHexInteger("Ox0")           --&gt; false (Begins with a capital 'o')
        isHexInteger("#0040")         --&gt; false
      </pre>

      Since NeXtMidas 2.7.4, a value starting with <code>"#"</code> will return false (not a hex value).
      @param str The string to test.
      @return <code>true</code> if the string represents a hex value and<br>
              <code>false</code> if the string does not represent a hex value.
      @see #isInteger(String)
      @see #isNumber(String)
      @since NeXtMidas 2.1.0
   */
  public static boolean isHexInteger (String str) {
    if (str == null) {
      return false;
    } else if (str.startsWith("0x") || str.startsWith("0X")) {
      return matchesRegex(HEX_VAL_REGEX, str.substring(2));
    // Since 2.7.4: below lines commented out per Bug 2030
    //} else if (str.startsWith("#")) {
    //  return matchesRegex(HEX_VAL_REGEX, str.substring(1));
    } else {
      return false;
    }
  }

  /** Tests to see if a string represents an floating point value. Values may
      be positive or negative. Positive values should not have a '+' or '-'
      sign while negative values should start with a '-' sign. Values may
      have a fractional part that starts with a '.' and an exponent that starts
      with an 'E', 'e' and has a value (exponent may contain a '+' or a '-' sign).
      <br>
      <br>
      Note that if <code>isInteger(str)=true</code> then <code>isFloat(str)=true</code>.
      <br>
      <br>
      Examples:
      <pre>
        isFloat("")              --&gt; false
        isFloat("-")             --&gt; false
        isFloat(null)            --&gt; false
        isFloat("42")            --&gt; true
        isFloat("10.7")          --&gt; true
        isFloat("-7.3e6")        --&gt; true
        isFloat("-7.e6")         --&gt; true
        isFloat("-.7e6")         --&gt; true
        isFloat("-7.0e+6")       --&gt; true
        isFloat("-7.0E-6")       --&gt; true
        isFloat("1E0")           --&gt; true
        isFloat("1E0.7")         --&gt; false (Has decimal in exponent)
        isFloat("NaN")           --&gt; true  (Since 2.5.0, see {@link #isNaN(String)})
        isFloat("Inf")           --&gt; true  (Since 2.5.0, see {@link #isInf(String)})
        isFloat("-Infinity")     --&gt; true  (Since 2.5.0, see {@link #isInf(String)})
      </pre>
      This does not check to see that the integer value falls within the
      valid range for a <code>float</code> or <code>double</code>.
      @param str The string to test.
      @return <code>true</code> if the string represents a floating point value (including the two
              special IEEE 754 values for NaN and Inf), <code>false</code> otherwise.
      @see #isNumber(String)
      @since NeXtMidas 2.1.0
   */
  public static boolean isFloat (String str) {
    return isInteger(str) || matchesRegex(FLOAT1_REGEX,str) || matchesRegex(FLOAT2_REGEX,str)
        || isNaN(str) || isInf(str);
  }

  /** Tests to see if a string represents the IEEE 754 value for Not-a-Number. This will return
      true iff the string is equal to "NaN" (ignoring case). Note that, unlike all other IEEE 754
      values, NaN can not be negative.<br>
      <br>
      Examples:
      <pre>
        isNaN("NaN")             --&gt; true
        isNaN("NAN")             --&gt; true
        isNaN("nan")             --&gt; true
        isNaN("42")              --&gt; false
        isNaN("-NaN")            --&gt; false
        isNaN("Inf")             --&gt; false
        isNaN(null)              --&gt; false
      </pre>
      @param str The string to test.
      @return <code>true</code> if the string represents a NaN, <code>false</code> otherwise.
      @see #isNumber(String)
      @since NeXtMidas 2.5.0
   */
  public static boolean isNaN (String str) {
    return "NaN".equalsIgnoreCase(str);
  }

  /** Tests to see if a string represents the IEEE 754 value for Infinity. This will return
      true iff the string is equal to "Inf", "Infinity", "\u221E", "-Inf", "-Infinity" or "-\u221E"
      (ignoring case). (Note: "\u221E" is the unicode infinity sign).<br>
      <br>
      Examples:
      <pre>
        isInf("Inf")             --&gt; true (used by C/C++)
        isInf("Infinity")        --&gt; true (used by Java, text)
        isInf("\u221E")          --&gt; true (used by Java, graphics)
        isInf("-Inf")            --&gt; true
        isInf("-Infinity")       --&gt; true
        isInf("-\u221E")         --&gt; true
        isInf("INF")             --&gt; true
        isInf("inf")             --&gt; true
        isInf("NaN")             --&gt; false
        isInf("42")              --&gt; false
        isInf(null)              --&gt; false
      </pre>
      @param str The string to test.
      @return <code>true</code> if the string represents a NaN, <code>false</code> otherwise.
      @see #isNumber(String)
      @since NeXtMidas 2.5.0
   */
  public static boolean isInf (String str) {
    if ((str == null) || (str.length() == 0)) return false;

    return ((str.charAt(0) == '-') && isInf(str.substring(1)))
        || str.equalsIgnoreCase("Inf")
        || str.equalsIgnoreCase("Infinity")
        || ((str.length() == 1) && (str.charAt(0) == UnicodeMap.INFINITY));
  }

  /** Indicates if a given string matches a specified regular expression. */
  private static boolean matchesRegex (Pattern regex, String str) {
    return (str != null) && regex.matcher(str).matches();
  }

  /** This will compare two version numbers. <br>
      <br>
      This is used to compare version numbers in the form "a.b.c" such as is
      used for the NeXtMidas version numbers. For example:
      <pre>
        n = compareVersions("1.9.0", "1.8.2")  --&gt; n &gt; 0
        n = compareVersions("1.9.0", "1.9.0")  --&gt; n = 0
        n = compareVersions("1.9.0", "1.9.2")  --&gt; n &lt; 0
      </pre>
      For convenience an <code>'X'</code> can be inserted in part of the version
      number indicating that it matches any value. Examples:
      <pre>
        n = compareVersions("1.9.X", "1.9.1")  --&gt; n = 0
        n = compareVersions("1.9.2", "1.9.X")  --&gt; n = 0
        n = compareVersions("1.9.X", "1.8.2")  --&gt; n &gt; 0
        n = compareVersions("1.X.2", "X.8.X")  --&gt; n = 0
      </pre>
      @param first  The first version number.
      @param second The second version number.
      @return &lt;0 If first is less than second.<br>
              =0 If both are equal.<br>
              &gt;0 If the second is less than the first.
      @throws NullPointerException     If either string is <code>null</code>.
      @throws IllegalArgumentException If both strings do not have the same
                                       number of dots ('.') in them or if
                                       the versions are not integers (other
                                       than the 'X' notation).
      @since NeXtMidas 1.9.2
   */
  public static int compareVersions (String first, String second) {
    /* Split on the dots */
    String[] firstNums  = first .toUpperCase().split("[.]");
    String[] secondNums = second.toUpperCase().split("[.]");
    //updated in 3.9.0 to support the new java version numbering
    int numDigitsCompare = Math.min(firstNums.length, secondNums.length);

    try {
      for (int ii = 0; ii < numDigitsCompare; ii++) {
        if (firstNums[ii].equals("X") || secondNums[ii].equals("X")) {
          /* 'X' notation - skip. */

        } else {
          int firstVal  = Integer.parseInt(firstNums[ii]);
          int secondVal = Integer.parseInt(secondNums[ii]);

          if (firstVal != secondVal) {
            return firstVal - secondVal; /* They differ */
          }
        }
      }
    } catch(NumberFormatException e) {
      throw new IllegalArgumentException("Invalid version number; given '"
                                       + first + "' and '" + second + "'.");
    }

    return firstNums.length - secondNums.length; // one with more updates
  }

  /** Converts a given Java (Unicode) String to a string containing only ASCII characters. <br>
      <br>
      This is the same as <code>toAsciiString(str,'?')</code>.
      @param str The origional string.
      @return A version of the origional string containing only ASCII characters.
      @since NeXtMidas 1.9.5
   */
  public static String toAsciiString (String str) {
    return toAsciiString(str,'?');
  }

  /** Converts a given Java (Unicode) String to a string containing only ASCII characters.
      @param str The origional string.
      @param sub The substitute character to use in place of any non-ASCII characters in the string.
      @return A version of the origional string containing only ASCII characters.
      @since NeXtMidas 1.9.5
   */
  public static String toAsciiString (String str, char sub) {
    char[] chars = str.toCharArray();
    boolean allAscii = true; /* Indicates if all chars are ASCII */

    for (int ii = 0; ii < chars.length; ii++) {
      if (chars[ii] > 127) {
        allAscii = false;
        chars[ii] = sub;
      }
    }

    return (allAscii)? str: new String(chars);
  }

  /** Converts an object to a NeXtMidas string. <br>
      <br>
      This method creates a new {@link StringBuffer} that is passed, with the given object, to
      {@link #addToStringBuffer(StringBuffer,Object)}. Then the string buffer is returned as a
      {@link String}.
      @param obj The object.
      @return The string version of the object.
      @see #addToStringBuffer(StringBuffer,Object)
   */
  public static String toString (Object obj) {
    StringBuffer strBuf = new StringBuffer(40);
    return addToStringBuffer(strBuf,obj).toString();
  }

  /** Adds the string value of a given object to a string buffer. <br>
      @param strBuf The string buffer to add to.
      @param obj    The object to add to it.
      @return The string buffer version of the object.
      @see Table#toStringBuffer(Object,StringBuffer,int)
   */
  public static StringBuffer addToStringBuffer (StringBuffer strBuf,  Object obj) {
    // Since 3.5.3 Bug 2876 pass in modified default Table flags (escape sequences disabled), rather than boolean
    int flagsPlusDisableEscape = Table.getDefaultFlagsInt() | Table.DISABLE_ESCAPE_SEQUENCES;
    Table.toStringBuffer(obj,strBuf,flagsPlusDisableEscape);
    return strBuf;
  }

  /** Converts URI escapes to their normal string values. This strictly follows the rules in
      section "2.1. Percent Encoding" of RFC 3986 - Uniform Resource Identifier (URI): General Syntax.
      <a href="http://tools.ietf.org/html/rfc3986">http://tools.ietf.org/html/rfc3986</a>
      <br>
      Percent Encoded Format: %hh (i.e. one percent followed by two hexadecimal digits).<br>
      For example: All "%20" (hex: 0x20, decimal: 32) will be converted to spaces.
      @param str The String to decode any percent encoding in it
      @return The String after replacing the percentage escape sequences with the applicable UTF-8 character
      @since NeXtMidas 2.9.0
   */
  public static String decodePercentEncoding (String str) {
    if (str == null) return null;
    boolean any = false;
    int max = str.length();
    int len = 0;
    byte[] chars = new byte[max];

    for (int ii=0; ii < max; ii++) {
      char c = str.charAt(ii);
      if (c == '%') {
        if ((ii + 2) >= max) throw new IllegalArgumentException("Illegal URL escape sequence in '"+str+"' at position "+ii );
        chars[len++] = (byte)Integer.parseInt(str.substring(ii+1,ii+3),16); // convert hex to byte
        any = true;
        ii += 2;
      }
      else {
        chars[len++] = (byte)c;
      }
    }

    return (any) ? new String(chars, 0, len) : str;
  }

  /** URL encode a string (see RFC 3986 - Uniform Resource Identifier (URI): General Syntax).
      Uses java.net.URLEncoder.encode(..) with "UTF-8" encoding and converts UnsupportedEncodingException
      to a runtime exception.
      @param str the String to URL encode
      @return percent-encoded/URL-encoded string, null if input is null
      @throws MidasException if unable to encode string
      @since NeXtMidas 3.5.3
   */
  public static String encodePercentEncoding (String str) {
    if (str == null) return null;
    try {
      str = URLEncoder.encode(str, "UTF-8");
    } catch (UnsupportedEncodingException e) {
      throw new MidasException("Unable to percent-encode str: " + str, e);
    }
    return str;
  }

  /** Gets the acronym for a string. The acronym for a string is composed of the capital letters
      in the string. <br>
      <br>
      Examples:
        <pre>
          getAcronym("getNewPage") = "NP"
          getAcronym("XStart")     = "XS"
          getAcronym("TimeOut")    = "TO"
          getAcronym("lowercase")  = ""
          getAcronym("")           = ""
          getAcronym(null)         = null
        </pre>
      @param str The string to get the acronym for.
      @return The acronym or <code>null</code> if <code>str==null</code>.
      @since NeXtMidas 2.1.0
   */
  public static String getAcronym (String str) {
    String returnValue = null;

    if (str != null) {
      StringBuilder acronym = new StringBuilder(8);

      for (int ii = 0; ii < str.length(); ii++) {
        if (Character.isUpperCase(str.charAt(ii))) {
          acronym.append(str.charAt(ii));
        }
      }

      returnValue = acronym.toString();
    }

    return returnValue;
  }

  /** Converts an IPv4 address as an integer to a string. For example:
      <pre>
        getIpAddressString(0x7f000001) = "127.0.0.1"
        getIpAddressString(0x0A0A0132) = "10.10.1.50"
      </pre>
      @param addr The address packed into an integer.
      @return The address as a string.
      @see #getIpAddressString(byte[])
      @since NeXtMidas 2.3.0
   */
  public static String getIpAddressString (int addr) {
    byte[] octets = new byte[] { (byte)((addr >> 24) & 0xFF), (byte)((addr >> 16) & 0xFF),
                                 (byte)((addr >>  8) & 0xFF), (byte)((addr      ) & 0xFF) };

    return getIpAddressString(octets);
  }

  /** Converts the octets of an IPv4 or IPv6 address a string. For example:
      <pre>
        getIpAddressString({ 127, 0, 0, 1})  = "127.0.0.1"
        getIpAddressString({ 10, 10, 1, 50}) = "10.10.1.50"
      </pre>
      @param octets The octets of an IPv4 or IPv6 address (the highest order byte should be at
                    <code>octets[0]</code>).
      @return The address as a string.
      @since NeXtMidas 2.3.0
   */
  public static String getIpAddressString (byte[] octets) {
    InetAddress inet = null;

    if (octets == null) {
      throw new MidasException("StringUtil.getIpAddressString: Given octets=null.");
    }

    try {
      inet = InetAddress.getByAddress(octets);
    } catch (UnknownHostException e) {
      throw new MidasException("StringUtil.getIpAddressString: Illegal number of octets specified "
                             + "for IP address. Expected 4 octets (IPv4) or 16 octets (IPv6), "
                             + "given "+octets.length+" octets.", e);
    }

    return inet.getHostAddress();
  }

  /** Packs the IP address for a IPv4 address into an integer. This will throw an exception in not
   *  given a valid IPv4 address (i.e. this will throw an exception every time an IPv6 address is
   *  seen). For example:
   *  <pre>
   *    getIpv4AddressNumber("127.0.0.1")  = 0x7f000001
   *    getIpv4AddressNumber("10.10.1.50") = 0x0A0A0132
   *  </pre>
   *  @param addr A valid IPv4 address.
   *  @return The address packed into an integer.
   *  @since NeXtMidas 2.3.0
   */
  public static int getIpv4AddressNumber (String addr) {
    InetAddress inet = null;
    try {
      inet = InetAddress.getByName(addr);
    } catch (UnknownHostException e) {
      throw new MidasException("StringUtil.getIpv4AddressNumber: Unknown host '"+addr+"'", e);
    }

    byte[] octets = inet.getAddress();

    if (octets.length != 4) {
      throw new MidasException("StringUtil.getIpv4AddressNumber: Host '"+addr+"' does not map to "
                             + "a valid IPv4 address.");
    }

    return ((((int)octets[0]) & 0xFF) << 24) | ((((int)octets[1]) & 0xFF) << 16)
         | ((((int)octets[2]) & 0xFF) <<  8) | ((((int)octets[3]) & 0xFF)      );
  }

  /** Convert HTML document to plain text.
   *  @param html String containing an HTML document.
   *  @return String containing text version of input HTML string
   *  @since NeXtMidas 2.5.0
   */
  public static String html2Text (String html) {
    String results = html;
    results = HTML2SPACES_REGEX.matcher(results).replaceAll(" ");
    results = HTML2NEWLINE_REGEX.matcher(results).replaceAll("\n");
    results = HTML_TAGS_REGEX.matcher(results).replaceAll("");
    results = HTML_LT_REGEX.matcher(results).replaceAll("<");
    results = HTML_GT_REGEX.matcher(results).replaceAll(">");
    results = HTML_AMP_REGEX.matcher(results).replaceAll("&");
    results = HTML_QUOTE_REGEX.matcher(results).replaceAll("\"");
    results = HTML_COPY_REGEX.matcher(results).replaceAll("[Copyright]");
    results = HTML_REG_REGEX.matcher(results).replaceAll("[Registered]");
    results = HTML_TRADE_REGEX.matcher(results).replaceAll("[Trademark]");
    return results;
  }

  // The following entity names and values taken from http://www.w3.org/TR/REC-html40/sgml/entities.html
  // "24 Character entity references in HTML 4" and then sorted alphabetically by name.
  //
  //       ENTITY_NAMES[i] <=> ENTITY_VALUES[i]
  private static final String[] ENTITY_NAMES = {
    "AElig",    "Aacute",   "Acirc",    "Agrave",   "Alpha",    "Aring",    "Atilde",   "Auml",     // 7
    "Beta",     "Ccedil",   "Chi",      "Dagger",   "Delta",    "ETH",      "Eacute",   "Ecirc",    // 15
    "Egrave",   "Epsilon",  "Eta",      "Euml",     "Gamma",    "Iacute",   "Icirc",    "Igrave",   // 23
    "Iota",     "Iuml",     "Kappa",    "Lambda",   "Mu",       "Ntilde",   "Nu",       "OElig",    // 31
    "Oacute",   "Ocirc",    "Ograve",   "Omega",    "Omicron",  "Oslash",   "Otilde",   "Ouml",     // 39
    "Phi",      "Pi",       "Prime",    "Psi",      "Rho",      "Scaron",   "Sigma",    "THORN",    // 47
    "Tau",      "Theta",    "Uacute",   "Ucirc",    "Ugrave",   "Upsilon",  "Uuml",     "Xi",       // 55
    "Yacute",   "Yuml",     "Zeta",     "aacute",   "acirc",    "acute",    "aelig",    "agrave",   // 63
    "alefsym",  "alpha",    "amp",      "and",      "ang",      "aring",    "asymp",    "atilde",   // 71
    "auml",     "bdquo",    "beta",     "brvbar",   "bull",     "cap",      "ccedil",   "cedil",    // 79
    "cent",     "chi",      "circ",     "clubs",    "cong",     "copy",     "crarr",    "cup",      // 87
    "curren",   "dArr",     "dagger",   "darr",     "deg",      "delta",    "diams",    "divide",   // 95
    "eacute",   "ecirc",    "egrave",   "empty",    "emsp",     "ensp",     "epsilon",  "equiv",    // 103
    "eta",      "eth",      "euml",     "euro",     "exist",    "fnof",     "forall",   "frac12",   // 111
    "frac14",   "frac34",   "frasl",    "gamma",    "ge",       "gt",       "hArr",     "harr",     // 119
    "hearts",   "hellip",   "iacute",   "icirc",    "iexcl",    "igrave",   "image",    "infin",    // 127
    "int",      "iota",     "iquest",   "isin",     "iuml",     "kappa",    "lArr",     "lambda",   // 135
    "lang",     "laquo",    "larr",     "lceil",    "ldquo",    "le",       "lfloor",   "lowast",   // 143
    "loz",      "lrm",      "lsaquo",   "lsquo",    "lt",       "macr",     "mdash",    "micro",    // 151
    "middot",   "minus",    "mu",       "nabla",    "nbsp",     "ndash",    "ne",       "ni",       // 159
    "not",      "notin",    "nsub",     "ntilde",   "nu",       "oacute",   "ocirc",    "oelig",    // 167
    "ograve",   "oline",    "omega",    "omicron",  "oplus",    "or",       "ordf",     "ordm",     // 175
    "oslash",   "otilde",   "otimes",   "ouml",     "para",     "part",     "permil",   "perp",     // 183
    "phi",      "pi",       "piv",      "plusmn",   "pound",    "prime",    "prod",     "prop",     // 191
    "psi",      "quot",     "rArr",     "radic",    "rang",     "raquo",    "rarr",     "rceil",    // 199
    "rdquo",    "real",     "reg",      "rfloor",   "rho",      "rlm",      "rsaquo",   "rsquo",    // 207
    "sbquo",    "scaron",   "sdot",     "sect",     "shy",      "sigma",    "sigmaf",   "sim",      // 215
    "spades",   "sub",      "sube",     "sum",      "sup",      "sup1",     "sup2",     "sup3",     // 223
    "supe",     "szlig",    "tau",      "there4",   "theta",    "thetasym", "thinsp",   "thorn",    // 231
    "tilde",    "times",    "trade",    "uArr",     "uacute",   "uarr",     "ucirc",    "ugrave",   // 239
    "uml",      "upsih",    "upsilon",  "uuml",     "weierp",   "xi",       "yacute",   "yen",      // 247
    "yuml",     "zeta",     "zwj",      "zwnj"
  };
  private static final char[] ENTITY_VALUES = {
    198,        193,        194,        192,        913,        197,        195,        196,        // 7
    914,        199,        935,        8225,       916,        208,        201,        202,        // 15
    200,        917,        919,        203,        915,        205,        206,        204,        // 23
    921,        207,        922,        923,        924,        209,        925,        338,        // 31
    211,        212,        210,        937,        927,        216,        213,        214,        // 39
    934,        928,        8243,       936,        929,        352,        931,        222,        // 47
    932,        920,        218,        219,        217,        933,        220,        926,        // 55
    221,        376,        918,        225,        226,        180,        230,        224,        // 63
    8501,       945,        38,         8743,       8736,       229,        8776,       227,        // 71
    228,        8222,       946,        166,        8226,       8745,       231,        184,        // 79
    162,        967,        710,        9827,       8773,       169,        8629,       8746,       // 87
    164,        8659,       8224,       8595,       176,        948,        9830,       247,        // 95
    233,        234,        232,        8709,       8195,       8194,       949,        8801,       // 103
    951,        240,        235,        8364,       8707,       402,        8704,       189,        // 111
    188,        190,        8260,       947,        8805,       62,         8660,       8596,       // 119
    9829,       8230,       237,        238,        161,        236,        8465,       8734,       // 127
    8747,       953,        191,        8712,       239,        954,        8656,       955,        // 135
    9001,       171,        8592,       8968,       8220,       8804,       8970,       8727,       // 143
    9674,       8206,       8249,       8216,       60,         175,        8212,       181,        // 151
    183,        8722,       956,        8711,       160,        8211,       8800,       8715,       // 159
    172,        8713,       8836,       241,        957,        243,        244,        339,        // 167
    242,        8254,       969,        959,        8853,       8744,       170,        186,        // 175
    248,        245,        8855,       246,        182,        8706,       8240,       8869,       // 183
    966,        960,        982,        177,        163,        8242,       8719,       8733,       // 191
    968,        34,         8658,       8730,       9002,       187,        8594,       8969,       // 199
    8221,       8476,       174,        8971,       961,        8207,       8250,       8217,       // 207
    8218,       353,        8901,       167,        173,        963,        962,        8764,       // 215
    9824,       8834,       8838,       8721,       8835,       185,        178,        179,        // 223
    8839,       223,        964,        8756,       952,        977,        8201,       254,        // 231
    732,        215,        8482,       8657,       250,        8593,       251,        249,        // 239
    168,        978,        965,        252,        8472,       958,        253,        165,        // 247
    255,        950,        8205,       8204
  };

  /** Converts a string to HTML, using escapes for any HTML control characters.
   *  Currently this supports only the following HTML conversions:
   *  <pre>
   *    &amp; -&lt; &amp;amp;
   *    &lt; -&lt; &amp;lt;
   *    &gt; -&lt; &amp;gt;
   *    &quot; -&lt; &amp;quot;
   *  </pre>
   *  Warning: Since this method does not convert all characters to their HTML versions,
   *           it is NOT symmetric with <code>fromHTML(String)</code>, e.g.
   *           <code> StringUtil.fromHTML(StringUtil.toHTML(orig_str))</code> may not be equal to orig_str.
   *  @param str The CharSequence (String, StringBuffer, StringBuilder) to convert.
   *  @return The converted string, or an empty String if input str is null.
   *  @since NeXtMidas 2.7.2
   *  @see #fromHTML(String)
   */
  public static String toHTML (CharSequence str) {
    if (str == null) return "";
    int len = str.length();
    StringBuilder sb = new StringBuilder(len*2);
    // JC4: This should probably be updated to include escaping any non-ASCII Unicode characters
    for (int ii=0; ii<len; ii++) {
      char ch = str.charAt(ii);
      switch (ch) {
        case '&': sb.append("&amp;");  break;
        case '<': sb.append("&lt;");   break;
        case '>': sb.append("&gt;");   break;
        case '"': sb.append("&quot;"); break;
        default : sb.append(ch);       break;
      }
    }
    return sb.toString();
  }

  /** Converts a HTML to String, converting any HTML escapes to their corresponding characters.
   *  Unlike {@link #toHTML}, this supports all of the HTML escapes as outlined on
   *  <a href="http://www.w3.org/TR/REC-html40/sgml/entities.html">
   *  http://www.w3.org/TR/REC-html40/sgml/entities.html</a> (last accessed 2007-12-26).
   *  @param str The string to convert.
   *  @return The converted string.
   *  @since NeXtMidas 2.7.2
   */
  public static String fromHTML (String str) {
    for (int index = str.indexOf('&',0); index >= 0; index = str.indexOf('&',index+1)) {
      int end = str.indexOf(';',index);

      if (end < index+2) {
        // assume a stray '&'
      }
      else if (str.charAt(index+1) == '#') {
        int num = Convert.s2l(str.substring(index+2,end));
        str = str.substring(0,index) + (char)num + str.substring(end+1);
      }
      else {
        int num = Arrays.binarySearch(ENTITY_NAMES, str.substring(index+1, end));
        if (num >= 0) {
          str = str.substring(0,index) + ENTITY_VALUES[num] + str.substring(end+1);
        }
      }
    }
    return str;
  }

  /** Add a given escape character(s) (usually a back-slash) to a given String in front of given chars
   *  @param str The String to parse
   *  @param stringToInsert The escape character(s) to use (default is {@value #ESCAPE_CHAR})
   *  @param charToEscape   The character that you want to put the escape in front of
   *  @return The String with escape characters
   *  @since NeXtMidas 2.9.0
   */
  public static String insertEscapeChar (String str, String stringToInsert, char charToEscape) {
    if (stringToInsert==null || stringToInsert.isEmpty()) {
      stringToInsert = Character.toString(ESCAPE_CHAR);
    }
    StringBuilder strBuilder = new StringBuilder(str);
    byte[] inbytes = str.getBytes();
    int offset = 0;  // how many changes were made to the original String

    for (int index=0; index<inbytes.length; index++) {
      if ((char)inbytes[index]==charToEscape) {
        strBuilder.insert(index+offset, stringToInsert);
        offset += stringToInsert.length();
      }
    }
    return strBuilder.toString();
   }

  /** Returns a synchronized (thread-safe) format backed by the specified format. In order to
   *  guarantee serial access, it is critical that all access to the backing format is accomplished
   *  through the returned format (or its views). <br>
   *  <br>
   *  The implementation of this method is modeled after that used by {@link Collections#synchronizedMap}.
   *  @param form The format to be "wrapped" in a synchronized format.
   *  @return A synchronized view of the specified format.
   *  @throws NullPointerException if <code>form</code> is null.
   *  @since NeXtMidas 2.7.2
   */
  public static Format synchronizedFormat (Format form) {
    if (form == null) throw new NullPointerException("Can not wrap null format.");
    return new SynchronizedFormat(form);
  }

  /** <b>Internal use only:</b> Synchronized wrapper for Format. */
  private static final class SynchronizedFormat extends Format {
    /** NOTE: this should be changed if the structure/fields of this class changes. */
    private static final long serialVersionUID = -2343446153995335036L;

    private final Format form;

    SynchronizedFormat (Format form) {
      this.form = form;
    }
    @Override
    public synchronized StringBuffer format (Object obj, StringBuffer toAppendTo, FieldPosition pos) {
      return form.format(obj, toAppendTo, pos);
    }
    @Override
    public synchronized AttributedCharacterIterator formatToCharacterIterator (Object obj) {
      return form.formatToCharacterIterator(obj);
    }
    @Override
    public synchronized Object parseObject (String source) throws ParseException {
      return form.parseObject(source);
    }
    @Override
    public synchronized Object parseObject (String source, ParsePosition pos) {
      return form.parseObject(source, pos);
    }
    @Override
    public int hashCode() {
      return form.hashCode();
    }
    @Override
    public boolean equals (Object obj) {
      return (obj instanceof SynchronizedFormat) && form.equals(((SynchronizedFormat)obj).form);
    }
    @Override
    public Object clone () {
      return super.clone();
    }
  }

  /** Unescapes some character literals to it's literal character in Java Strings using the backslash
   *  as the escape character. For example it will translate a sequence of '\' and 'n' into a newline
   *  character, unless the '\'  is preceded by another '\'.<br>
   *  <br>
   *  To allow "\n" in output escape that with an extra backslash (i.e. "\\n").
   *  <br>
   *  <table border="1">
   *    <caption style="visibility:hidden">""</caption>
   *    <tr><th>Escape Sequence (String)</th><th>Single Character Value (decimal / hex value) </th></tr>
   *    <tr><td> \b             </td><td>backspace      = BS  (08 / 08) </td></tr>
   *    <tr><td> \t             </td><td>horizontal tab = HT  (09 / 09) </td></tr>
   *    <tr><td> \n             </td><td>newline (LF)   = NL  (10 / 0A) </td></tr>
   *    <tr><td> \f             </td><td>form feed      = FF  (12 / 0C) </td></tr>
   *    <tr><td> \r             </td><td>carriage return= CR  (13 / 0D) </td></tr>
   *    <tr><td> \"             </td><td>double quote   = "   (34 / 22) </td></tr>
   *    <tr><td> \'             </td><td>single quote   = '   (39 / 27) Note: you normally do not need to escape single quote in a double-quoted string</td></tr>
   *    <tr><td> \\             </td><td>backslash      = \   (92 / 5C) </td></tr>
   *    <tr><td> &#92;uHHHH     </td><td>HHHH is the four hexadecimal digits representing the single 16-bit Unicode character.</td></tr>
   *    <tr><td> &#92;xHH       </td><td>HH is the two hexadecimal digits representing the single 8-bit character. [C/C++/JavaScript]</td></tr>
   *    <tr><td> \0             </td><td>the NUL char   = NUL (00 / 00) - special case [C/C++/JavaScript]</td></tr>
   *  </table>
   *  <br>
   *  There is currently no use case to convert the following escape sequences, although
   *  they can still be inserted via the <code>bashslash u</code> Unicode escape sequence noted above.
   *  <table border="1">
   *    <caption style="visibility:hidden">""</caption>
   *    <tr><th>Unsupported Escape Sequence (String)</th><th>Single Character Value (decimal / hex value) </th></tr>
   *    <tr><td> \a             </td><td>alert bell     = BEL (07 / 07) [C/C++] </td></tr>
   *    <tr><td> \v             </td><td>vertical tab   = VT  (11 / 0B) [C/C++] </td></tr>
   *    <tr><td> \?             </td><td>question mark  = ?   (63 / 3F) [C++] </td></tr>
   *    <tr><td>&#92;OOO        </td><td>Octal Escape (1, 2, or 3) octal digits. This is the old C style escapes (use the Unicode escape sequence noted above) [Java/C/C++/JavaScript].</td></tr>
   *  </table>
   *  <br>
   *  This method is based on org.apache.commons.lang.StringEscapeUtils.unescapeJava() v2.5.
   *  @param input text to convert/translate all occurrences of the described escape sequences to its char value.
   *  @return String with the escape sequences converted to their literal character values or null if input is null
   *  @throws MidasException if an invalid escape sequence is given (this is consistent with Java compiler (JLS) and System.out.printf(..) method)
   *  @since NeXtMidas 2.9.0
   */
  public static String unescapeString (CharSequence input) {
    if (input == null) {
      return null;
    }
    int len = input.length();
    StringBuilder outBuf = new StringBuilder(len);
    StringBuilder unicodeBuf = new StringBuilder(4); // for parsing Unicode escape sequence (4 hex digits to 2 bytes)
    StringBuilder hexBuf     = new StringBuilder(2); // for parsing Hex escape sequence (2 hex digits to 1 byte)
    boolean hadSlash  = false; // if read a backslash '\' (escape character)
    boolean inUnicode = false; // if processing a Unicode escape sequence (4 digits)
    boolean inHex     = false; // if processing a hex escape sequence (2 digits)

    for (int ii=0; ii<len; ii++) {
      char ch = input.charAt(ii);
      if (inUnicode) { // process Unicode escape sequence (backslash u + HHHH)
        unicodeBuf.append(ch);
        if (unicodeBuf.length() == 4) { // unicodeBuf now contains the four hex digits
          try {
            int value = Integer.parseInt(unicodeBuf.toString(), 16); // have to use Integer (signed Short is out of range)
            outBuf.append((char)value); // add Unicode value into output string
            unicodeBuf.setLength(0);    // reset buffer
            inUnicode = false;          // reset flags
            hadSlash  = false;
          }
          catch (NumberFormatException nfe) {
            throw new MidasException("Invalid Unicode escape sequence specified (4 hex digits): "+unicodeBuf, nfe);
          }
        }
        continue;
      }

      if (inHex) { // process Hex escape sequence (backslash x + HH)
        hexBuf.append(ch);
        if (hexBuf.length() == 2) {     // hexBuf now contains the two hex digits
          try {
            short value = Short.parseShort(hexBuf.toString(), 16); // have to use Short (signed Byte is out of range)
            outBuf.append((char)value); // need to cast to char value before adding into output string
            hexBuf.setLength(0);        // reset buffer
            inHex    = false;           // reset flags
            hadSlash = false;
          }
          catch (NumberFormatException nfe) {
            throw new MidasException("Invalid Hex escape sequence specified (2 hex digits): "+hexBuf, nfe);
          }
        }
        continue;
      }

      if (hadSlash) {     // handle escaped value
        hadSlash = false; // reset hadSlash flag
        switch (ch) { // ordered in most commonly used
          case  '"' : outBuf.append(QUOTE);  break; // convert to double quote
          case  'n' : outBuf.append(LF);     break; // convert to newline (linefeed)
          case  '\\': outBuf.append(BSLASH); break; // convert to single backslash
          case  'r' : outBuf.append(CR);     break; // convert to carriage return
          case  't' : outBuf.append(HTAB);   break; // convert to horizontal tab
          case  '\'': outBuf.append(FTIC);   break; // convert to single quote
          case  'f' : outBuf.append('\f');   break; // convert to form feed
          case  'b' : outBuf.append('\b');   break; // convert to backspace
          case  '0' : outBuf.append(NULL);   break; // convert to ASCII NUL char, i.e. \0
          case  'u' : inUnicode = true;      break; // start processing Unicode escape sequence
          case  'x' : inHex     = true;      break; // start processing Hex escape sequence
          default   : throw new MidasException("Invalid escape sequence specified: \\"+ch+" input=["+input+"]");
        }
      }
      else if (ch == BSLASH) {
        hadSlash = true; // start escape sequence
      }
      else {
        outBuf.append(ch); // straight passthrough of character
      }
    }

    if (inUnicode) {
      throw new MidasException("Invalid Unicode escape sequence specified (4 hex digits): "+unicodeBuf);
    }
    if (inHex) {
      throw new MidasException("Invalid Hex escape sequence specified (2 hex digits): "+hexBuf);
    }
    if (hadSlash) {
      throw new MidasException("Invalid escape sequence specified (single backslash): \\");
    }

    return outBuf.toString();
  }

  /** Add escape characters to a String in front of certain special or Unicode characters
   *  that cannot be handled in traditional representation, such as quotes, newlines,
   *  tabs, backspaces, etc. <br><br>Note: Does not make special case for leading and trailing quotes.<br>
   *  This method calls {@link #escapeString(CharSequence, boolean)} with false (single quotes are not escaped).
   *  @param input The String to parse
   *  @return A String with escape characters in front of special characters
   *  @since NeXtMidas 2.9.0
   */
  public static String escapeString (CharSequence input) {
    return escapeString(input,false);
  }

  /** Add escape characters to a String in front of certain special or Unicode characters
   *  that cannot be handled in traditional representation, such as quotes, newlines,
   *  tabs, backspaces, etc. <br><br>Note: Does not make special case for leading and trailing quotes.
   *  @param input              The String to parse.
   *  @param escapeSingleQuotes Support strings quoted with single-quotes?
   *  @return A String with escape characters in front of special characters
   *  @since NeXtMidas 2.9.0
   */
  public static String escapeString (CharSequence input, boolean escapeSingleQuotes) {
    if (input == null) {
      return null;
    }
    StringBuilder outBuf = new StringBuilder(input.length());
    char c;
    for (int index=0; index<input.length(); index++) {
      c = input.charAt(index);
      if (c=='\'' && escapeSingleQuotes) {
        outBuf.append('\\').append(c);
      }
      else if ( c==QUOTE || c==BSLASH || c==HTAB || c==LF | c==CR|| c=='\f' || c=='\b') {   // these only require a single char after the escape
        outBuf.append('\\');   // add the escape indicator (BSLASH)
        switch (c) {
          case QUOTE:  outBuf.append( c );  break; // just insert the character itself after the escape
          case BSLASH: outBuf.append( c );  break; // just insert the character itself after the escape
          case HTAB:   outBuf.append('t');  break; // tab
          case '\b':   outBuf.append('b');  break; // backspace
          case '\f':   outBuf.append('f');  break; // form feed
          case LF:     outBuf.append('n');  break; // newline
          case CR:     outBuf.append('r');  break; // carriage return
          default:                          break;
        }
      }
      else if ((c < SPACE) || (c > 126)) {  // other special characters which need to be escaped will use the \\uNNNN format
        outBuf.append('\\');
        outBuf.append('u');

        // get the number value of the special character and convert to a String
        short cint = (short)c;
        String hexC = Integer.toHexString(cint);
        switch (hexC.length()) {
          case 1:  hexC = "000"+hexC;  break;
          case 2:  hexC =  "00"+hexC;  break;
          case 3:  hexC =   "0"+hexC;  break;
          default:                     break;
        }
        outBuf.append(hexC);
      }
      else outBuf.append(c);
    }
    return outBuf.toString();
  }

  /** Convert map to a {@link StringBuilder} for debugging. Output with newline in appendStr:<pre>
   *  &lt;key1&gt; = &lt;value1&gt;
   *  &lt;key2&gt; = &lt;value2&gt;
   *  ...
   *  &lt;keyN&gt; = &lt;valueN&gt;</pre>
   *  <br>
   *  This is useful for debugging by having each key/value pair on a separate newline
   *  (with newline in either prependStr or appendStr).<br>
   *  @param map Map to convert to StringBuilder
   *  @param prependStr String to prepend to output before each entry (e.g. to indent output)
   *  @param appendStr  String to append to output before each entry (since 3.3.1)
   *  @param equalsStr  String to use for equals (null to default to " = ") between key/value (since 3.3.1)
   *  @return The new {@link StringBuilder}.
   *  @since NeXtMidas 3.1.1
   */
  @InternalUseOnly
  public static StringBuilder toStringBuilder (Map<?,?> map, String prependStr, String appendStr, String equalsStr) {
    StringBuilder sb = new StringBuilder(128);
    Iterator<?> entries = map.entrySet().iterator();
    if (equalsStr == null) equalsStr = " = ";

    while (entries.hasNext()) {
      Map.Entry<?,?> entry = (Map.Entry<?,?>)entries.next();
      Object key   = entry.getKey();
      Object value = entry.getValue();

      if (prependStr != null) sb.append(prependStr);
      sb.append(key).append(equalsStr).append(value);
      if (appendStr  != null) sb.append('\n');
    }
    return sb;
  }

  /** Convert an array to a StringBuilder for debugging or comma separated list.
   *  If numberize, then output is numbered. E.g. with numberize and newline appendStr:<pre>
   *  1. &lt;value1&gt;
   *  2. &lt;value2&gt;
   *  ...
   *  N. &lt;valueN&gt;</pre>
   *  <br>
   *  This is useful for debugging by having each element on a separate line (with newline in
   *  either prependStr or appendStr).<br>
   *  This is also useful to convert arrays to comma separated list (either with comma in
   *  prependStr (for leading) or appendStr (for trailing)).
   *  @param array Object array to convert to StringBuilder
   *  @param prependStr String to prepend to output before each entry (e.g. to indent output)
   *  @param appendStr  String to append to output before each entry (since 3.3.1)
   *  @param numberize true to add "#. " for each entry (one-based) (since 3.3.1)
   *  @return The new {@link StringBuilder}.
   *  @since NeXtMidas 3.1.1
   */
  @InternalUseOnly
  public static StringBuilder toStringBuilder (Object[] array, String prependStr, String appendStr, boolean numberize) {
    StringBuilder sb = new StringBuilder(128);
    int ii = 0;
    for (Object value : array) {
      if (prependStr != null) sb.append(prependStr);
      if (numberize) {  ii++; sb.append(ii).append(". "); }
                              sb.append(value);
      if (appendStr  != null) sb.append(appendStr);
    }
    return sb;
  }

  /** Gets the length to end of the valid variable name for expansion/subsitution.
   *  Valid variable names are: [A-Za-z0-9_.] (e.g. alphanumeric + underscore and dot).
   *  @param str   The string containing the variable name.
   *  @param index The index into the buffer to begin reading from.
   *  @return The valid variable name length, zero if no valid variable name.
   *  @since NeXtMidas 3.1.2 - based on {@link Results#validNameLength(String, int)}.
   */
  private static int validVariableLength (String str, int index) {
    int size = str.length();
    char c;
    for (int ii=index; ii<size; ii++) {
      c = str.charAt(ii);
           if (c>='A' && c<='Z') { /* valid char */ }
      else if (c>='a' && c<='z') { /* valid char */ }
      else if (c>='0' && c<='9') { /* valid char */ }
      else if (c=='_')           { /* valid char */ }
      else if (c=='.')           { /* valid char */ }
      else if (ii==index)        return 0;
      else                       return ii-index;
    }
    return size-index;
  }

  /** Expand (substitute/replace) variables (e.g. $ENVNAME or ${ENVNAME}) from input
   *  text string with values from specified key/value map.<br>
   *  <pre>
   *    This uses the dollar ('$') character to start a variable name.
   *    Valid variable names are: [A-Za-z0-9_.] (e.g. alphanumeric + underscore and dot).
   *    The variable name can optionally be surrounded by braces.
   *    This allows subsitution of variables within variables.
   *    Two consecutive dollars will escape (prevent) the variable subsitution from happening
   *    and removes one of dollar from result.
   *    e.g. for following valueMap:
   *      x = quick
   *      y = brown
   *      z = fox
   *      a = z
   *
   *      "The $x ${y} ${$a} jumped over $$a ; $non_existent_key $a. ${a}."
   *      --&gt; "The quick brown fox jumped over $a ; $non_existent_key $a. z."
   *  </pre>
   *  @param text     input string to expand variables
   *  @param valueMap map of variables names (keys) and values to substitute with
   *  @param flags    mask of options to control variable expansion behavior.
   *                  valid options are {@link #NO_ERROR}. pass zero for no options.
   *  @return text string with variables expanded/substitute. null if input text is null.
   *  @since NeXtMidas 3.1.2
   */
  public static String expandVariables (String text, Map<String,String> valueMap, int flags) {
    if (text==null) return text;
    int iDollar,j,textLen;
    String tmp;
    Object obj;
    for (j=textLen=text.length(); (iDollar=text.lastIndexOf('$',j))>=0; j=iDollar-1) {
      if (iDollar>1 && text.charAt(iDollar-1)=='$') { // two $ escapes variable name expansion
        iDollar--;
        text = text.substring(0,iDollar) + text.substring(iDollar+1); // remove escaped dollar
        continue;
      }
      int k;
      if (text.charAt(iDollar+1)==LCURLY) {
        k = text.indexOf(RCURLY,iDollar);
        if (k < iDollar+1) { // If the curly braces do not match (i.e. "{ ... }")
          if ((flags&NO_ERROR)!=0) continue; // NO_ERROR option was specified, ignore and continue
          throw new MidasException("Syntax Error: Can not expand $ variable: Missing closing '}' in \""
            + text + "\" at char index "+iDollar);
        }
        tmp = text.substring(iDollar+2,k);
        obj = valueMap.get(tmp);
        k++;
      }
      else {
        j = validVariableLength(text,iDollar+1);
        if (j<1) continue;
        k = iDollar+j+1;
        tmp = text.substring(iDollar+1,k);
        obj = valueMap.get(tmp);
      }
      if (obj==null) continue;
      tmp = obj.toString();
      if (k<textLen) tmp = tmp+text.substring(k);
      if (iDollar==0) return tmp;
      text = text.substring(0,iDollar) + tmp;
      textLen = text.length();
    }
    return text;
  }

  /** Converts a string to title case.
   *  @param s The string.
   *  @return The title-case version.
   *  @since NeXtMidas 3.9.0
   */
  public static String toTitleCase (String s) {
    if ((s == null) || s.isEmpty()) return s;

    boolean anyChanges = false;
    boolean fixNext    = true;
    char[]  chars      = s.toCharArray();

    for (int i = 0; i < s.length(); i++) {
      char c = chars[i];

      if (Character.isSurrogate(c)) {
        return _toTitleCaseCP(s);
      }
      else if (Character.isWhitespace(c)) {
        fixNext = true;
      }
      else if (fixNext) {
        chars[i]   = Character.toTitleCase(c);
        anyChanges = true;
        fixNext    = false;
      }
    }

    return (anyChanges)? new String(chars) : s;
  }

  /** Converts a string to title case using code points (this is rarely needed) and should only
   *  be called by {@code #toTitleCase(..)}.
   *  @param s The string.
   *  @return The title-case version.
   *  @since NeXtMidas 3.9.0
   */
  private static String _toTitleCaseCP (String s) {
    boolean fixNext = true;
    int[]   chars   = s.codePoints().toArray();

    for (int i = 0; i < s.length(); i++) {
      int c = chars[i];

      if (Character.isWhitespace(c)) {
        fixNext = true;
      }
      else if (fixNext) {
        chars[i]   = Character.toTitleCase(c);
        fixNext    = false;
      }
    }
    return new String(chars, 0, chars.length);
  }

  /** Creates a list of strings from the given object. This will accept:
   *  <pre>
   *    - null (which yields an empty list)
   *    - ""   (which yields an empty list)
   *    - a {@link Collection}
   *    - a comma-separated list (e.g. "a,b,c")
   *  </pre>
   *  @param obj The value or values.
   *  @return The list.
   *  @since NeXtMidas 3.9.0
   */
  @SuppressWarnings("unchecked")
  public static List<String> asStrList (Object obj) {
    if (obj == null) {
      return EMPTY_LIST;
    }
    else if (obj instanceof Collection) {
      Collection<?> c = (Collection<?>)obj;

      if (c.isEmpty()) {
        return EMPTY_LIST;
      }
      if ((c instanceof List) && c.stream().allMatch((v) -> (v == null) || (v instanceof String))) {
        return (List<String>)obj;
      }

      List<String> list = new ArrayList<>(c.size());
      c.forEach((v) -> list.add(v.toString()));
      return list;
    }
    else {
      String str = obj.toString().trim();

      if (str.startsWith("[") && str.endsWith("]")) {
        str = str.substring(1,str.length()-1).trim();
      }

      return (str.isEmpty())? EMPTY_LIST : Arrays.asList(str.split(",[ ]*"));
    }
  }

  /** Creates a set of strings from the given object. This will accept:
   *  <pre>
   *    - null (which yields an empty list)
   *    - ""   (which yields an empty list)
   *    - a {@link Collection}
   *    - a comma-separated list (e.g. "a,b,c")
   *  </pre>
   *  @param obj The value or values.
   *  @return The set.
   *  @since NeXtMidas 3.9.0
   */
  public static SortedSet<String> asStrSet (Object obj) {
    List<String> list = asStrList(obj);
    return (list == EMPTY_LIST)? EMPTY_SET : new TreeSet<>(list);
  }

  /** Get a word from a string containing a substring. <br>
      For example, getWordByString("This is a string","ri") returns "string"
      @param inString  Input string
      @param subString The substring for which to search
      @return Word containing sub string
      @since NeXtMidas 3.9.2 moved here from intr.Sedit for jdk 11 modularity
   */
  public static String getWordByString (String inString, String subString) {
    Parser p = new Parser (inString);
    String outString = null;
    String word;

    for (int i = 1; i <= p.elements(); i++) {
      word = p.get(i);
      if (word.indexOf (subString) >= 0) {
        outString = word;
      }
    }
    return outString;
  }

  /** Get a word from a string delimited by given character.
      @param inString    Input string
      @param elemNum     Element number (1..n)
      @param delimString The delimiter DEFAULT=comma
      @return The word.
      @since NeXtMidas 3.9.2 moved here from intr.Sedit for jdk 11 modularity
   */
  public static String getWordByElement (String inString, int elemNum, String delimString) {
    String outString = null; // The DEFAULT
      if (delimString == null || delimString.length() == 0) {
        delimString = ",";
      }
      String[] strArray = inString.split(StringUtil.textToRegex(delimString));
      if (elemNum > 0 && (elemNum-1 < strArray.length) ) {
        outString = strArray[elemNum-1];
      }
      return outString;
  }

  /** This will substitute occurrances of a substring (<code>p1</code>) in
      a string (<code>string</code>) with a new string <code>p2</code>. <br>
          If <code>all</code> is <code>true</code>, this is the same as
      <code>StringUtil.replaceAll(string,p1,p2)</code><br>
      If <code>all</code> is <code>false</code>, this is the same as
      <code>StringUtil.replaceFirst(string,p1,p2)</code>
      @param string The to make replacements on.
      @param p1     The plain-text pattern identifying what is to be replaced.
      @param p2     The plain-text replacement for anything that matches the pattern.
      @param all    If this is <code>true</code> ALL occurrances of <code>p1</code> are
                    replaced with <code>p2</code>. If this is <code>false</code> only the
                    FIRST occurrance of <code>p1</code> are replaced with <code>p2</code>.
      @return A new version of <code>string</code> where occurrances of
              <code><i>p1</i></code> have been replaced with <code><i>p2</i></code>.
      @see StringUtil#replaceAll(String,String,String)
      @see StringUtil#replaceFirst(String,String,String)
      @since NeXtMidas 3.9.2 moved here from intr.Sedit for jdk 11 modularity
   */
  public static String subs (String string, String p1, String p2, boolean all) {
    if (all) {
      return StringUtil.replaceAll(string,p1,p2);
    }
    else {
      return StringUtil.replaceFirst(string,p1,p2);
    }
  }

  /** Returns number of elements in a comma delimited string. <br>
      This is the same as <code>nelements(string,',')</code>.
      @param string - Input string
      @return The number of elements (tokens) in the string.
      @see #nelements(String,String)
      @since NeXtMidas 3.9.1 moved here from intr.Sedit for jdk 11 modularity
   */
  public static int nelements (String string) {
    return nelements(string, ",");
  }

  /** Returns number of elements in a delimited string. <br>
      @param string  Input string
      @param delim - delimiter
      @return number of elements
      @since NeXtMidas 3.9.1 moved here from intr.Sedit for jdk 11 modularity
  */
  public static int nelements (String string, char delim) {
    return nelements(string, String.valueOf(delim));
  }

  /** Returns number of elements in a delimited string. <br>
      Examples:
        <pre>
        nelements(" ", ",")               --&gt; 0
        nelements(",", ",")               --&gt; 2
        nelements("a and b and c", "and") --&gt; 3
      </pre>
      @param string - Input string
      @param delim - delimiter
      @return number of elements
      @since NeXtMidas 3.9.1 moved here from intr.Sedit for jdk 11 modularity
  */
  public static int nelements (String string, String delim) {
    int retval = 0;
    if (string == null || string.length() == 0 ||
        string.equalsIgnoreCase("NULL")) {
      // No elements
    }
    else {
      char[] charArray = new char[string.length()];
      char[] delimArray = new char[delim.length()];
      char[] subCharArray = new char[delim.length()];
      string.getChars(0, string.length(), charArray, 0);
      delim.getChars(0, delim.length(), delimArray, 0);
      int numDelims = 0;
      for (int i = 0; i <= charArray.length - delimArray.length; i++) {
        if (charArray[i] == delimArray[0]) {
          subCharArray = Arrays.copyOfRange(charArray, i, i + delimArray.length);
          if (Arrays.equals(subCharArray, delimArray)) numDelims++;
        }
      }
      retval = numDelims + 1;
    }
    return retval;
  }

  /** Extracts the "inclusive" substring from <code>string</code> between <code>p1</code> and
      <code>p2</code>.  Indices are zero-based.  Negative numbers represent relative indices.
      @param string Input string
      @param p1     Start index (0 to len-1) of substring (INCLUSIVE)
      @param p2     End index (0 to len-1) of substring (INCLUSIVE)
      @return The substring.
      @since NeXtMidas 3.9.2 moved here from intr.Sedit for jdk 11 modularity
  */
  public static String range (String string, int p1, int p2) {
    if (p2<0) p2 = string.length()+p2;
    else p2 = p2 + 1;
    if (p1<0) p1 = string.length()+p1-1;
    if ((p2<p1) || (p1>string.length())) return null;
    p2 = Math.max(p1,Math.min(string.length(),p2));
    return string.substring(p1,p2);
  }

  /** Extracts the "inclusive" substring from <code>inString</code> between <code>imin</code> and
      <code>imax</code>.  Indices are zero-based.  Negative numbers represent an index
      relative to the last character in the input string.
      @param inString Input string
      @param imin     Start index (0 to len-1) of substring (INCLUSIVE)
      @param imax     End index (0 to len-1) of substring (INCLUSIVE)
      @return The substring, if in range, or "" if imin&gt;imax or any part of the min or max is beyond the range of the input String
      @since NeXtMidas 3.1.2
      @since NeXtMidas 3.9.2 moved here from intr.Sedit for jdk 11 modularity
   */
  public static String getStringInRange (String inString, int imin, int imax) {
    if (imin<0) { imin = inString.length() + imin; }
    if (imax<0) { imax = inString.length() + imax; }
    imax++; // make it inclusive
    if (imin<0 || imax>inString.length() || imin>imax || imax<0 || imin>inString.length()) return "";
    return inString.substring(imin,imax);

  }

  /** Find the nth instance of a substring in an input string.
      @param inString  Input string
      @param subString Sub string to find in input string
      @return The index of the substring or <code>-1</code> if not found.
      @since NeXtMidas 3.9.2 moved here from intr.Sedit for jdk 11 modularity
   */
  public static int select (String inString, String subString) {
    int retVal = Parser.find(inString,subString,-1);
    if (retVal > 0) retVal = retVal-1;
    return retVal;
  }


  /** Wrapper for padding LEFT Right or BOTH (center)
      @param string Input string
      @param newLen Width of output string
      @param dir Direction to pad PADLeft or LEFT, PADRight or RIGHT, PADBoth or BOTH
      @param padStr First character of this string is used to pad
      @return Padded string or original string if longer than desired input length
      @see nxm.sys.lib.StringUtil#padLeft
      @see nxm.sys.lib.StringUtil#padRight
      @see nxm.sys.lib.StringUtil#padBoth
      @since NeXtMidas 3.9.2 moved here from intr.Sedit for jdk 11 modularity
   */
  public static String pad (String string, int newLen, String dir, String padStr) {
    char pad = ' ';
    if (padStr != null && padStr.length() != 0) {
      pad = padStr.charAt(0);
    }
    String outString = null;
    if (dir.startsWith("PADL") || dir.equals("LEFT")) {
      outString = StringUtil.padLeft(string, newLen, pad);
    }
    else if (dir.startsWith("PADB") || dir.equals("BOTH") || dir.equals("CENTER")) {
      // Pad both sides, add one to left if odd
      outString = StringUtil.padBoth(string, newLen, pad);
    }
    else {
      outString = StringUtil.padRight(string, newLen, pad);
    }
    return outString;
  }

  /** Find the index of the nth instance of a substring in an input string.
    @param inString  Input string
    @param subString Sub string to find in input string
    @param n occurrence of subString to find index of
    @return The index of the nth occurrence of substring or <code>-1</code> if nth occurrence not found.
    @since NeXtMidas 3.9.2 moved here from intr.Sedit for jdk 11 modularity
   */
  public static int getOccurrenceOf (String inString, String subString, int n) {
    int index = inString.indexOf(subString);
    for (int i = 0; i < n-1; i++) {
      index = inString.indexOf(subString, index+subString.length());
      if (index > inString.length() || index < 0) {
        index = -1;
        break;
      }
    }
    return index;
  }

  /** Find the nth instance of a substring in an input string.
      @param inString  Input string
      @param subString Sub string to find in input string
      @return The index of the substring or <code>-1</code> if not found.
      @since NeXtMidas 3.9.2 moved here from intr.Sedit for jdk 11 modularity
   */
  public static int selectElement (String inString, String subString) {
    return Parser.find(inString,subString,-1);
  }

  /** Calculates the length of string through the Nth naturally delimited entry.
      Zero-based index.
      @param string Input string
      @param index  Entry number (0..n)
      @return The length
      @since NeXtMidas 3.9.2 moved here from intr.Sedit for jdk 11 modularity
  */
  public static int length (String string, int index) {
    if (index < 0) return string.length();
    Parser p = new Parser(string);
    int ls = p.lengthd(0);
    for (int i = 1; i < index + 1; i++) {
      ls++;
      ls += p.lengthd(ls);
    }
    return ls;
  }

  /** Calculates the length of string through the Nth naturally delimited entry.
      One-based index.
      @param string Input string
      @param index  Entry number (0..n)
      @return The length.
      @since NeXtMidas 3.9.2 moved here from intr.Sedit for jdk 11 modularity
  */
  public static int lengthElement (String string, int index) {
    if (index <= 0) return string.length();
    Parser p = new Parser(string);
    int ls = p.lengthd(0);
    for (int i = 1; i < index; i++) {
      ls++;
      ls += p.lengthd(ls);
    }
    return ls;
  }

  /** Format a number.  Wrapper to  {@link nxm.sys.lib.Format} class
      @param dval Number to be formation
      @param pattern Format string pattern
      @return Formatted number as a string
      @see nxm.sys.lib.Format
      @since NeXtMidas 3.9.2 moved here from intr.Sedit for jdk 11 modularity
  */
  public static String nform (double dval, String pattern) {
    Data D = new Data(dval);
    return D.toString(pattern);
  }

  /** Parses given element in string by given delimiter.  <br>
      This is the same as <code>StringUtil.getParsedElement(string,element,delim,clean)</code>.
      @param string   The string to parse.
      @param element  The index of the element to get.
      @param delim    The delimiter
      @param clean    Indicates that the string should be cleaned while parsing.
      @return The element within the parsed string at the specified index or
              <code>""</code> if <code>index</code> is invalid.
      @see StringUtil#getParsedElement(String,int,char,boolean)
      @see StringUtil#getParsedElement(String,int,boolean)
      @since NeXtMidas 3.9.2 moved here from intr.Sedit for jdk 11 modularity
  */
  public static String parse (String string, int element, String delim, boolean clean) {
    if (delim == null || delim.length() == 0) {
      return StringUtil.getParsedElement(string, element, clean);
    }
    else {
      return StringUtil.getParsedElement(string, element, delim.charAt(0), clean);
    }
  }

  /** Parses string into ALL elements by given delimiter or "natural" comma or space delimiter. <br>
      @param string   The string to parse.
      @param elements The maximum number of elements to get when parsing.
      @param delim    The delimiter character as a String
      @param clean    Indicates that the string should be cleaned while parsing.
      @return An array of the first <i><code>elements</code></i> elements in the string.
      @see StringUtil#parseString(String,boolean)
      @see StringUtil#parseString(String,int,boolean)
      @since NeXtMidas 3.9.2 moved here from intr.Sedit for jdk 11 modularity
  */
  public static String[] parseAll (String string, int elements, String delim, boolean clean) {
    if (delim == null || delim.length() == 0) {
      return StringUtil.parseString(string, elements, clean);
    }
    else {
      return StringUtil.parseString(string, elements, delim.charAt(0), clean);
    }
  }

  /** Checks to see if a command support matches.
   *  @since NeXtMidas 3.9.2 moved here from intr.Sedit for jdk 11 modularity
   *  @param s1 First string value to check
   *  @param s2 Second string value to check
   *  @return true if they have a common character
   */
  @InternalUseOnly
  public static boolean containsCommonCharacter (String s1, String s2) {
    for (int i = 0; i < s1.length(); i++) {
      char s = s1.charAt(i);
      if (s2.indexOf(s) >= 0) return true;
    }
    return false;
  }
}
