package nxm.sys.test.junit.lib;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.Stack;
import java.util.Vector;

import org.junit.Test;

import nxm.sys.inc.AsciiMap;
import nxm.sys.inc.Keyable;
import nxm.sys.inc.UnicodeMap;
import nxm.sys.lib.Data;
import nxm.sys.lib.KeyVector;
import nxm.sys.lib.Midas;
import nxm.sys.lib.MidasException;
import nxm.sys.lib.StringUtil;
import nxm.sys.lib.Table;
import nxm.sys.test.junit.NMTestCase;

/** This will test the StringUtil class.

    @author  J. Czechowski IV
 */
public final class StringUtilTest extends NMTestCase implements TestValues {
  /** Strings which should map to null. */
  private static final String[] nullStrings    = new String[] {null,"NULL","null","Null","NuLl"};

  /** Strings which should not map to null. */
  private static final String[] notNullStrings = new String[] {" NULL ","a null","72","abc"};

  /** empty CharSequences/Strings ({@link StringUtil#isEmpty(CharSequence)} should return true). */
  private static final CharSequence[] emptyCharSeqs = {null, "", new StringBuilder(""), new StringBuffer()}; // since 3.3.1

  /** non-empty CharSequences/Strings ({@link StringUtil#isEmpty(CharSequence)} AND {@link StringUtil#isNullOrEmpty(Object)} should return false). */
  private static final CharSequence[] nonEmptyCharSeqs = {" ", "0", "abc", new StringBuilder("  "), new StringBuffer("1")}; // since 3.3.1

  /** Strings that are integer values. */
  private static final String[] intStrings    = { "42", "01", "-7", "1024", "10" };

  /** Strings that are not integer values. */
  private static final String[] notIntStrings = { null,"","-","+7","ONE","1O","1e1","1.0"};

  /** Strings that are not hex integer values. */
  private static final String[] hexStrings    = { "0x0040", "0x1A", "0X0", "0xCafeBabe", "0x1de" };

  /** Strings that are not hex integer values. */
  private static final String[] notHexStrings = { null, "", "ONE", "0X", "#", "27", "Ox0" }; // Last entry has 'o'

  /** Strings that are float values. */
  private static final String[] floatStrings    = { "10.7","-7.36","-7.e6","-.7e6","-7.0e+6","-7.0E-6","1E0"};

  /** Strings that are not float values. */
  private static final String[] notFloatStrings = { "",null,"1E0.7","1eO" };

  /** Strings that are numeric values. */
  private static final String[] notNumberStrings = { null,"A","YEA","NAY","NULL","ZEBRA","DUDE" };

  /** Strings that are NaN values. */
  private static final String[] nanStrings      = { "NaN", "nan", "NAN", "nAn" };

  /** Strings that are not NaN values. */
  private static final String[] notNanStrings   = { "",null,"1E0.7","-42","-1eO","Inf","7","-NaN" };

  /** Strings that are Inf values. (\u221E = INFINITY) */
  private static final String[] infStrings      = { "Inf", "INF", "inf", "Infinity", "INFINITY",
                                                    "infinity", ""+UnicodeMap.INFINITY,
                                                    "-Inf", "-INF", "-inf", "-Infinity", "-INFINITY",
                                                    "-infinity", "-"+UnicodeMap.INFINITY,
                                                    "\u221E", "-\u221E"};

  /** Strings that are not Inf values. */
  private static final String[] notInfStrings   = { "",null,"1E0.7","-42","1eO","NaN","7" };


  private static final Object[] TEST_TO_STRING
          = { null,                              "null",
              "abc",                             "\"abc\"",
              new Data(4),                       "L:4",
              new Data(new int[] {1, 2, 3}),     "L:(1,2,3)",
              new Data(new long[] {1L, 2L, 3L}), "X:(1,2,3)",
              new double[] {1.0, 2.0, 3.0},      "(1.0,2.0,3.0)",
              new String[] {"A", "B", "C"},      "(\"A\",\"B\",\"C\")" };

  private static final String[] TO_STRING_KEYS     = {"ONE", "TWO", "THREE"};
  private static final String[] TO_STRING_VALS     = {"1", "2", "3"};
  private static final String   TO_STRING_VAL_STR  = "(\"1\",\"2\",\"3\")";
  private static final String[] TO_STRING_KEY_STRS = { "{ONE=\"1\",TWO=\"2\",THREE=\"3\"}",
                                                       "{ONE=\"1\",THREE=\"3\",TWO=\"2\"}",
                                                       "{TWO=\"2\",ONE=\"1\",THREE=\"3\"}",
                                                       "{TWO=\"2\",THREE=\"3\",ONE=\"1\"}",
                                                       "{THREE=\"3\",ONE=\"1\",TWO=\"2\"}",
                                                       "{THREE=\"3\",TWO=\"2\",ONE=\"1\"}" };

  private static final byte[][] IPV4_ADDRESSES_OCTETS = { {127,0,0,1}, {10,10,1,50}, {(byte)255,(byte)255,(byte)255,0} };
  private static final String[] IPV4_ADDRESSES_STR    = { "127.0.0.1", "10.10.1.50", "255.255.255.0" };
  private static final int[]    IPV4_ADDRESSES_INT    = { 0x7f000001,  0x0A0A0132,   0xffffff00 };

  private static final byte[][] IPV6_ADDRESSES_OCTETS = { {0,0,0,0,0,0,0,0,0,0,0,0,127,0,0,1} };
  private static final String[] IPV6_ADDRESSES_STR    = { "0:0:0:0:0:0:7f00:1" };

  /** public constructor to allow running this test case directly with JUnit. */
  public StringUtilTest (String methodName) { this(methodName, null); }

  /** Creates a new instance of the class.
      @param methodName The name of the method being called.
      @param midas      The midas context to use when running the tests.
   */
  public StringUtilTest (String methodName, Midas midas) {
    super(methodName, midas);
  }

  //////////////////////////////////////////////////////////////////////////////
  private static final TestFense[] FENSE_TESTS = {
    //             0         1         2         3         4         5
    //             012345678901234567890123456789012345678901234567890123456789
    new TestFense("test {str{foo}}",  5, 14, "{}"),
    new TestFense("test {str{foo}}",  9, 13, "{}"),
    new TestFense("test (str(foo))",  5, 14, "()"),
    new TestFense("test (str(foo))",  9, 13, "()"),
    new TestFense("test (str(foo))", -1, -1, "{}"), // <-- special case, not found
    new TestFense(null,              -1, -1, "{}"), // <-- special case, check for null
    new TestFense("testfile.prm{LAYER={LT=2D},TAG=TEST}{FG=PRM}", 12, 35, "{}"),
    new TestFense("testfile.prm{LAYER={LT=2D},TAG=TEST}{FG=PRM}", 19, 25, "{}"),
    new TestFense("testfile.prm{LAYER={LT=2D},TAG=TEST}{FG=PRM}", 36, 43, "{}"),

    // 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.
    //             0         1         2         3         4         5
    //             012345678901234567890123456789012345678901234567890123456789
    new TestFense("I said \"Hello world!\"",     7, 20, "\"\""),
    new TestFense("I said \"Hello world!\"",    -1, -1, "''"),
    new TestFense("This is an empty string ''", 24, 25, "''"),
    new TestFense("''",                          0,  1, "''"),
  };

  public void testGetClosingFensePos() {
    for (TestFense tf : FENSE_TESTS) {
      if (tf.opening == -1) continue; // skip error case for getOpeningFensePos
      assertEquals("getClosingFensePos('"+tf.string+"', '"+""+tf.fense+"', "+tf.opening+")", tf.closing,
                   StringUtil.getClosingFensePos(tf.string, tf.fense, tf.opening));
    }
  }

  public void testGetOpeningFensePos() {
    for (TestFense tf : FENSE_TESTS) {
      if (tf.closing == -1) continue; // skip error case for getClosingFensePos
      assertEquals("getOpeningFensePos('"+tf.string+"', '"+""+tf.fense+"', "+tf.closing+")", tf.opening,
                   StringUtil.getOpeningFensePos(tf.string, tf.fense, tf.closing));
    }
  }

  public void testJoin() {
    assertEquals("a,b,c",   StringUtil.join(new String[] {"a", "b", "c"}, ","));
    assertEquals("a b c",   StringUtil.join(new String[] {"a", "b", "c"}, " "));
    assertEquals("a, b, c", StringUtil.join(new String[] {"a", "b", "c"}, ", "));
    assertEquals("a, b, c", StringUtil.join(new String[] {"a", "b", "c"}));
  }

  public void testToColumns() {
    final Object[] objs  = new Object[] { Integer.valueOf(1), Integer.valueOf(2),
                                          Integer.valueOf(3), Integer.valueOf(4),
                                          Integer.valueOf(5), Integer.valueOf(6) };
    final String objsRes = "1  2  3\n"
                         + "4  5  6";

    final String[] strs  = new String[] { "cat", "dog", "mouse", "frog" };
    final String strsRes = "cat    dog\n"
                         + "mouse  frog";
    final String altRes  = "cat     dog\r"
                         + "mouse   frog";
    final String longRes = "cat\n"
                         + "dog\n"
                         + "mouse\n"
                         + "frog";

    assertEquals(objsRes, StringUtil.toColumns(objs, 8));
    assertEquals(strsRes, StringUtil.toColumns(strs, 13));
    assertEquals(altRes,  StringUtil.toColumns(strs, 13, "   ", "\r"));
    assertEquals(longRes, StringUtil.toColumns(strs, 5));
  }

  public void testTrim() {
    assertEquals("0.49",    StringUtil.trim("Bananas $0.49/lb", "$", "/"));
    assertEquals("Bananas ",StringUtil.trim("Bananas $0.49/lb", "#", "$"));
    assertEquals("$0.49/lb",StringUtil.trim("Bananas $0.49/lb", " ", "#"));
  }

  public void testSubstring() {
    final String str = "The quick brown fox";

    assertEquals("Test 1", "The quick",       StringUtil.substring(str, 0,   8));
    assertEquals("Test 2", "quick",           StringUtil.substring(str, 4,   8));
    assertEquals("Test 3", "quick brown fox", StringUtil.substring(str, 4,  50));
  }

  public void testStripQuotes() {
    assertEquals(null,        StringUtil.stripQuotes((String)null));
    assertEquals("abc",       StringUtil.stripQuotes("abc"));
    assertEquals("abc",       StringUtil.stripQuotes("\"abc"));
    assertEquals("abc",       StringUtil.stripQuotes("\"abc"));
    assertEquals(" \"abc\" ", StringUtil.stripQuotes(" \"abc\" "));
  }

  public void testPadLeft() {
    assertEquals("..cat", StringUtil.padLeft("cat", 5, '.'));
    assertEquals("  cat", StringUtil.padLeft("cat", 5));
  }

  public void testPadRight() {
    assertEquals("cat..", StringUtil.padRight("cat", 5, '.'));
    assertEquals("cat  ", StringUtil.padRight("cat", 5));
  }

  public void testPadBoth() {
    assertEquals(".cat.", StringUtil.padBoth("cat", 5, '.'));
    assertEquals(" cat ", StringUtil.padBoth("cat", 5));
  }

  public void testIsWhitespace() {
    final String[] isWS  = { "     ", "  \n\n \t  ", "" };
    final String[] notWS = { "abc", "   \n\tabc\n", "     abc", "     abc     ",
                             "123     ", "^*%"};

    for(int i = 0; i < isWS.length; i++) {
      assertTrue("Test is WS " + i + ": ", StringUtil.isWhitespace(isWS[i]));
    }

    for(int i = 0; i < notWS.length; i++) {
      assertFalse("Test non-WS " + i + ": ", StringUtil.isWhitespace(notWS[i]));
    }
  }

  public void testToHTML()   {} // tested in testGetHTMLFor()
  public void testFromHTML() {} // tested in testGetHTMLFor()

  @SuppressWarnings(value={"deprecation"}) // to ignore StringUtil.getHTMLFor (since we are testing deprecated functionality)
  public void testGetHTMLFor() {
    final String[] text = { "abc",
                            "a > b",
                            "a < b",
                            "<b>a&c</b>",
                            "a < b > c",
                            "a && b",
                            "<<>>&&\"\"..", // test case for Bug 1967
                          };
    final String[] html = { "abc",
                            "a &gt; b",
                            "a &lt; b",
                            "&lt;b&gt;a&amp;c&lt;/b&gt;",
                            "a &lt; b &gt; c",
                            "a &amp;&amp; b",
                            "&lt;&lt;&gt;&gt;&amp;&amp;&quot;&quot;..",
                          };

    for(int i = 0; i < text.length; i++) {
      assertEquals("getHTMLFor() test " + i + ": ", html[i], StringUtil.getHTMLFor(text[i]));
      assertEquals("toHTML() test "     + i + ": ", html[i], StringUtil.toHTML(text[i]));
      assertEquals("fromHTML()test "    + i + ": ", text[i], StringUtil.fromHTML(html[i]));
    }
  }

  public void testConvertNullsToEmpty() {
    final String[] nulls    = new String[] {null,"NULL","null","Null","NuLl"};
    final String[] nonNulls = new String[] {" NULL ","a null","72","abc"};
    final Object[] nullObj  = new Object[] {(Object)null,(Integer)null,"null"};
    final Object[] objs     = new Object[] {"a null",Integer.valueOf(0)};

    for (int i = 0; i < nulls.length; i++) {
      assertEquals("Null String " + i, "", StringUtil.convertNullsToEmpty(nulls[i]));
    }

    for (int i = 0; i < nonNulls.length; i++) {
      assertSame("Non-Null String " + i, nonNulls[i], StringUtil.convertNullsToEmpty(nonNulls[i]));
    }

    for (int i = 0; i < nullObj.length; i++) {
      assertEquals("Null Object " + i, "", StringUtil.convertNullsToEmpty(nullObj[i]));
    }

    for (int i = 0; i < objs.length; i++) {
      assertSame("Non-Null Object " + i, objs[i], StringUtil.convertNullsToEmpty(objs[i]));
    }
  }

  public void testGetKeyValue() {
    final String[] keys = new String[] {"name", "height", "age"};
    final String[] vals = new String[] {"smith", null, "42"};

    final String spaces  = "name=smith age=42";
    final String colons  = "name:smith age:42";
    final String special = "name:==smith age:==42";

    for (int i = 0; i < keys.length; i++) {
      assertEquals("Test 1",vals[i],StringUtil.getKeyValue(spaces, keys[i]));
      assertEquals("Test 2",vals[i],StringUtil.getKeyValue(spaces, keys[i],"="));
      assertEquals("Test 3",vals[i],StringUtil.getKeyValue(colons, keys[i],":"));
      assertEquals("Test 4",vals[i],StringUtil.getKeyValue(special,keys[i],":=="));
    }
  }

  public void testTextToRegex() {
    assertEquals("Test 1", "abc",         StringUtil.textToRegex("abc"));
    assertEquals("Test 2", "abc[.]txt",   StringUtil.textToRegex("abc.txt"));
    assertEquals("Test 3", "1[+]2[*]3",   StringUtil.textToRegex("1+2*3"));

    // Bad test case, see Bug 2592
    //assertEquals("Test 4", "\\\\(a\\\\)", StringUtil.textToRegex("(a)"));
    assertEquals("Test 4", "\\(a\\)",     StringUtil.textToRegex("(a)"));

    assertEquals("Test 5", "\\\\\\\\",    StringUtil.textToRegex("\\\\"));
    assertEquals("Test 6", "\\\\\n",      StringUtil.textToRegex("\\\n"));

    char[] ESCAPES_SLASH   = { '(', ')', '[', ']', '{', '}',
                               '^' };
    char[] ESCAPES_BRACKET = { '.', '$', '+', '|', '&', ':',
                               '=', '-', '!', '<', '>' };
    String[] expArray = { "abc", "def" };

    // Bug 2592 -- run all regex values through String.split(..) to make sure
    // they actually work. Prior to this '^' was improperly escaped as "[^]"
    for (char c : ESCAPES_SLASH) {
      String regex = StringUtil.textToRegex("12"+c+"34");
      String str   = "abc12"+c+"34def";
      assertEquals("textToRegex("+c+")", "12\\"+c+"34",  regex);
      assertArrayEquals(str+".split("+regex+")", expArray, str.split(regex));
    }

    for (char c : ESCAPES_BRACKET) {
      String regex = StringUtil.textToRegex("12"+c+"34");
      String str   = "abc12"+c+"34def";
      assertEquals("textToRegex("+c+")", "12["+c+"]34",  regex);
      assertArrayEquals(str+".split("+regex+")", expArray, str.split(regex));
    }
  }

  public void testWildcardsToRegex() {
    assertEquals("Test 1", "abc",       StringUtil.wildcardsToRegex("abc"));
    assertEquals("Test 2", "abc[.]txt", StringUtil.wildcardsToRegex("abc.txt"));
    assertEquals("Test 3", "abc.*",     StringUtil.wildcardsToRegex("abc*"));
    assertEquals("Test 4", ".*_.*",     StringUtil.wildcardsToRegex("*_*"));
    assertEquals("Test 5", ".*[.]...",  StringUtil.wildcardsToRegex("*.???"));
  }

  public void testTextToReplacement() {
    assertEquals("Test 1", "abc",         StringUtil.textToReplacement("abc"));
    assertEquals("Test 2", "\\$19.95/lb", StringUtil.textToReplacement("$19.95/lb"));
  }

  public void testReplaceAll() {
    final String str = "The small dog.";

    assertEquals("Test 1", "The,small,dog.", StringUtil.replaceAll(str," ",","));
    assertEquals("Test 2", "The small cat.", StringUtil.replaceAll(str,"dog","cat"));
    assertEquals("Test 3", "The small dog.", StringUtil.replaceAll(str,"elf","cat"));
  }

  public void testReplaceFirst() {
    final String str = "The small dog.";

    assertEquals("Test 1", "The,small dog.", StringUtil.replaceFirst(str," ",","));
    assertEquals("Test 2", "The small cat.", StringUtil.replaceFirst(str,"dog","cat"));
    assertEquals("Test 3", "The small dog.", StringUtil.replaceFirst(str,"elf","cat"));
  }

  public void testSplit() {
    final String[]   values = new String[] { "The quick brown fox",
                                             "The quick brown fox",
                                             "The,quick,brown,fox",
                                             "",
                                             "USS",
                                             "SOS",
                                             "SSS",
                                             "SOO",
                                             "OOS",
                                             "OSO"};
    final String[]   delims = new String[] { " ", "$", ",",
                                             "S","S","S","S","S","S","S"};
    final String[][] res    = new String[][] {
                                new String[] {"The", "quick", "brown", "fox"},
                                new String[] {"The quick brown fox"},
                                new String[] {"The", "quick", "brown", "fox"},
                                new String[0],
                                new String[] {"U", ""},
                                new String[] {"", "O"},
                                new String[] {"", "", ""},
                                new String[] {"", "OO"},
                                new String[] {"OO"},
                                new String[] {"O", "O"}, };

    assert values.length == delims.length:
           "Number of delimiters does not match number of values.";
    assert values.length == res.length:
           "Number of results does not match number of values.";

    for (int i = 0; i < values.length; i++) {
      String[] test = StringUtil.split(values[i], delims[i]);

      assertEquals("Test " + i, res[i].length, test.length);

      for(int j = 0; j < test.length; j++) {
        assertEquals("Test " + i + "." + j, res[i][j], test[j]);
      }
    }
  }

  public void testCleanSplit() {
    // NOTE: spaces & tab have been intentionally injected into test values[] array
    final String[]   values = new String[] { "The quick brown fox",
                                             " The quick brown fox \t ",   // 1 (tab)
                                             "  The,quick,brown,,fox ,,,", // 2 (extra commas)
                                             "",        // 3
                                             "USS",
                                             "SOS",
                                             "SSS",     // 6
                                             "SOO",
                                             " OOS",
                                             "  OSO "}; // 9
    final String[]   delims = new String[] { " ", "$", ",",
                                             "S","S","S","S","S","S","S" };
    final String[][] exp    = new String[][] {
                                new String[] {"The", "quick", "brown", "fox"},
                                new String[] {" The quick brown fox \t "}, // 1
                                new String[] {"  The", "quick", "brown", "fox "},
                                new String[0],           // 3
                                new String[] {"U"},
                                new String[] {"O"},
                                new String[0],           // 6
                                new String[] {"OO"},
                                new String[] {" OO"},
                                new String[] {"  O", "O "}, // 9
                              };

    for (int i = 0; i < values.length; i++) {
      String[] test = StringUtil.cleanSplit(values[i], delims[i]);

      assertEquals("Test cleanSplit(str,delim) " + i, exp[i].length, test.length);
      for (int j = 0; j < test.length; j++) {
        assertEquals("Test cleanSplit(str,delim) " + i + "." + j, exp[i][j], test[j]);
      } // end inner for loop
    } // end outer for loop

    // since 3.1.2: test cleanSplit(String) method (using common midas delimiters (comma, pipe, whitespace)
    final String[] expStrArr = new String[] {"The", "quick", "brown", "fox"};
    final String[][] exp2 = new String[][] {
                                expStrArr,
                                expStrArr,            // 1
                                expStrArr,
                                new String[0],        // 3
                                new String[] {"USS"},
                                new String[] {"SOS"},
                                new String[] {"SSS"}, // 6
                                new String[] {"SOO"},
                                new String[] {"OOS"},
                                new String[] {"OSO"}, // 9
                            };
    for (int i = 0; i < values.length; i++) {
      String[] test = StringUtil.cleanSplit(values[i]);
      assertArrayEquals("Test cleanSplit(str) " + i+"=\""+values[i]+"\"", exp2[i], test);
    }
    String[] test = StringUtil.cleanSplit("  The ,,quick|||brown\n\rfox ,|\t\n\r");
    assertArrayEquals("Test cleanSplit(str) mixed (pipe, comma, whitespace)", expStrArr, test);
    // end since 3.1.2
  }

  public void testGetParsedElement() {
    final String str = "The quick brown fox";

    assertEquals("Test 1", "quick", StringUtil.getParsedElement(str,  2, false));
    assertEquals("Test 2", "QUICK", StringUtil.getParsedElement(str,  2, true));
    assertEquals("Test 3", "FOX",   StringUtil.getParsedElement(str,  4, true));
    assertEquals("Test 4", "",      StringUtil.getParsedElement(str, 99, true));
    assertEquals("Test 5", "",      StringUtil.getParsedElement(str, 99, false));
  }

  public void testGetParsedElementIndex() {
    final String str = "The quick brown fox";

    assertEquals("Test 1", -1, StringUtil.getParsedElementIndex(str, "QUICK", false));
    assertEquals("Test 2",  1, StringUtil.getParsedElementIndex(str, "quick", false));

    assertEquals("Test 3",  1, StringUtil.getParsedElementIndex(str, "QUICK", true));
    assertEquals("Test 4",  1, StringUtil.getParsedElementIndex(str, "quick", true));

    assertEquals("Test 5",  3, StringUtil.getParsedElementIndex(str, "FOX", true));

    assertEquals("Test 6", -1, StringUtil.getParsedElementIndex(str, "cat", true));
    assertEquals("Test 7", -1, StringUtil.getParsedElementIndex(str, "cat", false));
  }

  public void testParseString() {
    final String   str  = "The quick   brown   fox";
    final String[] resA = {"The", "quick", "brown", "fox"};
    final String[] resB = {"THE", "QUICK", "BROWN", "FOX"};

    for(int i = 1; i < 6; i++) {
      String[] testA = StringUtil.parseString(str, i, false);
      String[] testB = StringUtil.parseString(str, i, true);
      String[] testC = StringUtil.parseString(str, false);
      String[] testD = StringUtil.parseString(str, true);

      /*
      assertEquals("Test "+i+".1", Math.min(i, resA.length), testA.length);
      assertEquals("Test "+i+".2", Math.min(i, resB.length), testB.length);

      for(int j = 0; j < testA.length; j++) {
        assertEquals("Test "+i+".3."+j+".1", resA[j], testA[j]);
        assertEquals("Test "+i+".3."+j+".2", resB[j], testB[j]);
      }


      assertEquals("Test "+i+".4", resA.length, testC.length);
      assertEquals("Test "+i+".5", resA.length, testD.length);

      for(int j = 0; j < testC.length; j++) {
        System.out.println("testC[j] = " + testC[j]);
        assertEquals("Test "+i+".6."+j+".1", resA[j], testC[j]);
        assertEquals("Test "+i+".6."+j+".2", resB[j], testD[j]);
      }
       */
    }
  }

  public void testCleanString() {
    assertEquals("Test 1", "I,SAID,\"hello\",TO,HER.",
                 StringUtil.cleanString("I said \"hello\"   to her."));

    assertEquals("Test 2", "3.14",   StringUtil.cleanString("3.14"));

    assertEquals("Test 3", "YES,NO", StringUtil.cleanString("YES NO"));
    assertEquals("Test 4", "YES,NO", StringUtil.cleanString(" YES   NO "));
    assertEquals("Test 5", "YES,NO", StringUtil.cleanString("yes no"));
    assertEquals("Test 6", "YES,NO", StringUtil.cleanString(" yes   no "));
    assertEquals("Test 7", "YES,NO", StringUtil.cleanString("YES,NO"));
    assertEquals("Test 8", "YES,NO", StringUtil.cleanString("yes,no"));
  }

  public void testIsWildcardString() {
    assertTrue("Test 1", StringUtil.isWildcardString("abc*"));
    assertTrue("Test 2", StringUtil.isWildcardString("a?b"));
    assertTrue("Test 3", StringUtil.isWildcardString("a?*b"));

    assertFalse("Test 4", StringUtil.isWildcardString("abc"));
    assertFalse("Test 5", StringUtil.isWildcardString("ab.cd"));
  }

  public void testIsWildcardMatch() {
    assertTrue("Test 1", StringUtil.isWildcardMatch("abc", "abc"));
    assertTrue("Test 2", StringUtil.isWildcardMatch("a?c", "abc"));
    assertTrue("Test 3", StringUtil.isWildcardMatch("a?c", "a&c"));
    assertTrue("Test 4", StringUtil.isWildcardMatch("a*c", "aac"));
    assertTrue("Test 5", StringUtil.isWildcardMatch("a*c", "abc"));
    assertTrue("Test 6", StringUtil.isWildcardMatch("a*c", "a  c"));

    assertFalse("Test 7", StringUtil.isWildcardMatch("a?c", "abq"));
    assertFalse("Test 8", StringUtil.isWildcardMatch("a*c", "abq"));
    assertFalse("Test 9", StringUtil.isWildcardMatch("ab*", "bq"));
  }

  public void testIsNull () {
    testIsNull(nullStrings,    true);
    testIsNull(notNullStrings, false);
  }

  public void testIsNullOrEmpty () { // since 3.3.1
    testIsNullOrEmpty(nullStrings, true);
    testIsNullOrEmpty(notNullStrings, false);
    testIsNullOrEmpty(emptyCharSeqs, true);
    testIsNullOrEmpty(nonEmptyCharSeqs, false);
  }

  public void testIsEmpty () { // since 3.3.1
    testIsEmpty(emptyCharSeqs, true);
    testIsEmpty(nonEmptyCharSeqs, false);
    CharSequence[] moreNonEmptyList = {"null", "a NULL", new StringBuilder("  "), new StringBuffer("NULL")};
    testIsEmpty(moreNonEmptyList, false);
  }

  public void testIsTrue() {
    testIsTrue(TRUE_STRINGS,  true);
    testIsTrue(FALSE_STRINGS, false);
    testIsTrue(JUNK_STRINGS,  false);
  }

  public void testIsFalse() {
    testIsFalse(TRUE_STRINGS,  false);
    testIsFalse(FALSE_STRINGS, true);
    testIsFalse(JUNK_STRINGS,  false);
  }

  public void testIsNumber() {
    testIsNumber(intStrings,       true);
    testIsNumber(hexStrings,       true);
    testIsNumber(floatStrings,     true);
    testIsNumber(nanStrings,       true);
    testIsNumber(infStrings,       true);
    testIsNumber(notNumberStrings, false);
  }

  public void testIsInteger() {
    testIsInteger(intStrings,       true);
    testIsInteger(hexStrings,       false);
    testIsInteger(notNumberStrings, false);
    testIsInteger(notIntStrings,    false);
  }

  public void testIsHexInteger() {
    testIsHexInteger(hexStrings,       true);
    testIsHexInteger(notNumberStrings, false);
    testIsHexInteger(notHexStrings,    false);
    testIsHexInteger(intStrings,       false);
  }

  public void testIsFloat() {
    testIsFloat(floatStrings,     true);
    testIsFloat(nanStrings,       true);
    testIsFloat(infStrings,       true);
    testIsFloat(notNumberStrings, false);
    testIsFloat(notFloatStrings,  false);
  }

  public void testIsNaN() {
    testIsNaN(nanStrings,       true);
    testIsNaN(notNanStrings,    false);
  }

  public void testIsInf() {
    testIsInf(infStrings,       true);
    testIsInf(notInfStrings,    false);
  }

  public void testCompareVersions() {
    assertTrue("Test 1", StringUtil.compareVersions("9.0.1", "1.8.2") > 0);
    assertTrue("Test 2", StringUtil.compareVersions("9.0.1", "9.0.1") ==0);
    assertTrue("Test 3", StringUtil.compareVersions("9.0.1", "10.0.1") < 0);
    assertTrue("Test 1", StringUtil.compareVersions("10.0", "9.0.1") > 0);
    assertTrue("Test 1", StringUtil.compareVersions("10.0.x", "1.8.x") > 0);

    /* 'X' notation */
    assertTrue("Test 4", StringUtil.compareVersions("1.9.X", "1.9.1") ==0);
    assertTrue("Test 5", StringUtil.compareVersions("1.9.1", "1.9.X") ==0);
    assertTrue("Test 6", StringUtil.compareVersions("1.9.X", "1.8.2") > 0);
    assertTrue("Test 7", StringUtil.compareVersions("1.X.2", "X.8.X") ==0);
    /* lowercase 'x' */
    assertTrue("Test 8", StringUtil.compareVersions("1.9.x", "1.9.1") ==0);
    assertTrue("Test 9", StringUtil.compareVersions("1.9.1", "1.9.x") ==0);
    assertTrue("Test 10",StringUtil.compareVersions("1.9.x", "1.8.2") > 0);
    assertTrue("Test 11",StringUtil.compareVersions("1.x.2", "x.8.x") ==0);
  }

  public void testToAsciiString() {
    String badStr   = "12"+UnicodeMap.DEGREE_SIGN+" 7"+UnicodeMap.MICRO_SIGN+"s";
    String goodStr  = "12' 7us";
    String goodStr1 = "12? 7?s";
    String goodStr2 = "12_ 7_s";

    assertEquals("Test 1", goodStr1, StringUtil.toAsciiString(badStr));
    assertEquals("Test 2", goodStr1, StringUtil.toAsciiString(badStr, '?'));
    assertEquals("Test 3", goodStr2, StringUtil.toAsciiString(badStr, '_'));
    assertEquals("Test 4", goodStr,  StringUtil.toAsciiString(goodStr));
    assertEquals("Test 5", goodStr,  StringUtil.toAsciiString(goodStr, '?'));
    assertSame(  "Test 6", goodStr,  StringUtil.toAsciiString(goodStr, '_'));
  }


  public void testToString() {
    for(int i = 0; i < TEST_TO_STRING.length; i+=2) {
      String name = "toString(TEST_TO_STRING["+i+"])";
      assertEquals(name,  TEST_TO_STRING[i+1], StringUtil.toString(TEST_TO_STRING[i]));
    }

    Keyable   myKeyable   = new MyKeyable();
    Table     myTable     = new Table();
    KeyVector myKeyVector = new KeyVector(3);
    List<String> myList   = new Vector<>();
    Map<String,String> myMap = new Hashtable<>();

    for (int i = 0; i < TO_STRING_KEYS.length; i++) {
      myTable.setKey(TO_STRING_KEYS[i], TO_STRING_VALS[i]);
      myKeyable.setKey(TO_STRING_KEYS[i], TO_STRING_VALS[i]);
      myKeyVector.setKey(TO_STRING_KEYS[i], TO_STRING_VALS[i]);
      myMap.put(TO_STRING_KEYS[i], TO_STRING_VALS[i]);
      myList.add(TO_STRING_VALS[i]);
    }

    assertEquals("Table",     myTable.toString(),     StringUtil.toString(myTable));
    assertEquals("KeyVector", myKeyVector.toString(), StringUtil.toString(myKeyVector));
    assertEquals("List",      TO_STRING_VAL_STR,      StringUtil.toString(myList));
    assertTrue("Keyable", matchesOne(StringUtil.toString(myKeyable), TO_STRING_KEY_STRS));
    assertTrue("Map",     matchesOne(StringUtil.toString(myMap),     TO_STRING_KEY_STRS));
  }


  public void testAddToStringBuffer() {
    for(int i = 0; i < TEST_TO_STRING.length; i+=2) {
      String name = "toString(TEST_TO_STRING["+i+"])";
      StringBuffer sb = new StringBuffer("***");

      assertEquals(name,  "***"+TEST_TO_STRING[i+1],
                          StringUtil.addToStringBuffer(sb,TEST_TO_STRING[i]).toString());
    }

    Keyable   myKeyable   = new MyKeyable();
    Table     myTable     = new Table();
    KeyVector myKeyVector = new KeyVector(3);
    List<String> myList   = new Vector<>();
    Map<String,String> myMap = new Hashtable<>();

    for (int i = 0; i < TO_STRING_KEYS.length; i++) {
      myTable.setKey(TO_STRING_KEYS[i], TO_STRING_VALS[i]);
      myKeyable.setKey(TO_STRING_KEYS[i], TO_STRING_VALS[i]);
      myKeyVector.setKey(TO_STRING_KEYS[i], TO_STRING_VALS[i]);
      myMap.put(TO_STRING_KEYS[i], TO_STRING_VALS[i]);
      myList.add(TO_STRING_VALS[i]);
    }

    StringBuffer sb1 = new StringBuffer("***");  StringUtil.addToStringBuffer(sb1, myKeyable);
    StringBuffer sb2 = new StringBuffer("***");  StringUtil.addToStringBuffer(sb2, myTable);
    StringBuffer sb3 = new StringBuffer("***");  StringUtil.addToStringBuffer(sb3, myKeyVector);
    StringBuffer sb4 = new StringBuffer("***");  StringUtil.addToStringBuffer(sb4, myList);
    StringBuffer sb5 = new StringBuffer("***");  StringUtil.addToStringBuffer(sb5, myMap);

    assertTrue("Keyable   (starts with '***')", sb1.toString().startsWith("***"));
    assertTrue("Table     (starts with '***')", sb2.toString().startsWith("***"));
    assertTrue("KeyVector (starts with '***')", sb3.toString().startsWith("***"));
    assertTrue("List      (starts with '***')", sb4.toString().startsWith("***"));
    assertTrue("Map       (starts with '***')", sb5.toString().startsWith("***"));

    assertTrue("Keyable", matchesOne(sb1.toString().substring(3), TO_STRING_KEY_STRS));
    assertEquals("Table",     myTable.toString(),     sb2.toString().substring(3));
    assertEquals("KeyVector", myKeyVector.toString(), sb3.toString().substring(3));
    assertEquals("List",      TO_STRING_VAL_STR,      sb4.toString().substring(3));
    assertTrue("Map",     matchesOne(sb5.toString().substring(3), TO_STRING_KEY_STRS));
  }

  public void testGetAcronym() {
    final String[] strings = { "getNewPage", "XStart", "TimeOut", "lowercase", "", null };
    final String[] results = { "NP",         "XS",     "TO",      "",          "", null };

    for(int i = 0; i < strings.length; i++) {
      assertEquals("getAcronym('"+strings[i]+"')", results[i], StringUtil.getAcronym(strings[i]));
    }
  }

  public void testGetIpAddressString() {
    for (int i = 0; i < IPV4_ADDRESSES_STR.length; i++) {
      String ip  = IPV4_ADDRESSES_STR[i];
      String str = "getIpAddressString for "+ip+" ";

      assertEquals(str+"(int)",    ip, StringUtil.getIpAddressString(IPV4_ADDRESSES_INT[i]));
      assertEquals(str+"(octets)", ip, StringUtil.getIpAddressString(IPV4_ADDRESSES_OCTETS[i]));
    }

    for (int i = 0; i < IPV6_ADDRESSES_STR.length; i++) {
      String ip  = IPV6_ADDRESSES_STR[i];
      String str = "getIpAddressString for "+ip+" ";

      assertEquals(str+"(octets)", ip, StringUtil.getIpAddressString(IPV6_ADDRESSES_OCTETS[i]));
    }

  }

  public void testGetIpv4AddressNumber() {
    for (int i = 0; i < IPV4_ADDRESSES_STR.length; i++) {
      String ip  = IPV4_ADDRESSES_STR[i];
      String str = "getIpv4AddressNumber("+ip+")";

      assertEquals(str, IPV4_ADDRESSES_INT[i], StringUtil.getIpv4AddressNumber(ip));
    }
  }

  public void testHtml2Text() {
    String testhtml1 = "all white space before newline should be removed   \n  ";
    String expected1 = "all white space before newline should be removed\n  ";
    String test1     = StringUtil.html2Text(testhtml1);
    assertEquals("HTML2Text - white spaces before newline", expected1, test1);

    String testhtml2 = "all white space before newline should be removed  \n\r\t \n should be 2nd line ";
    String expected2 = "all white space before newline should be removed\n should be 2nd line ";
    String test2    = StringUtil.html2Text(testhtml2);
    assertEquals("HTML2Text white spaces [ \\r\\t\\n] before newline", expected2, test2);

    String html = "<html><head><title>unit test for StringUtil.html2Text</title></head>\n" +
                  "<body>Hello&nbsp;\tWorld! &lt;&quot;&amp; StringUtil &copy;&reg;&trade;&gt;" +
                  "<nonexistenttag></nonexistenttag><yetanothernonexistenttag>" +
                  "unittest<br/>one<br /> two<br>  three<br/><br>two BRs before this</body></html>";
    String expected = "unit test for StringUtil.html2Text\n" +
      "Hello  World! <\"& StringUtil [Copyright][Registered][Trademark]>" +
      "unittest\n" +
      "one\n" +
      " two\n" +
      "  three\n\n" +
      "two BRs before this";
    String text = StringUtil.html2Text(html);
    assertEquals("HTML2Text last", expected, text);
  }

  /** @since NeXtMidas 2.7.0 */
  public void testLtrim() {
    final String[] values = new String[] { " \t\r\n The quick brown fox \t\r\n",
                                           "The quick brown fox",
                                           " \t\r\n",
                                           "",
                                         };
    final String[] res    = new String[] { "The quick brown fox \t\r\n",
                                           "The quick brown fox",
                                           "",
                                           "",
                                         };

    assert values.length == res.length:
           "Number of results does not match number of values.";

    for(int i = 0; i < values.length; i++) {
      String test = StringUtil.ltrim(values[i]);

      assertEquals("Test ltrim: " + i, res[i], test);
    }
  }

  /** @since NeXtMidas 2.7.0 */
  public void testRtrim() {
    final String[] values = new String[] { " \t\r\n The quick brown fox \t\r\n",
                                           "The quick brown fox",
                                           " \t\r\n",
                                           "",
                                         };
    final String[] res    = new String[] { " \t\r\n The quick brown fox",
                                           "The quick brown fox",
                                           "",
                                           "",
                                         };

    assert values.length == res.length:
           "Number of results does not match number of values.";

    for(int i = 0; i < values.length; i++) {
      String test = StringUtil.rtrim(values[i]);

      assertEquals("Test rtrim: " + i, res[i], test);
    }
  }

  public void testInsertEscapeChar () {
    String testStr = "a\"quote";
    String result = StringUtil.insertEscapeChar(testStr, "FOO", AsciiMap.QUOTE);
    assertEquals("Insert EscapeStr:", result.substring(1,4).equals("FOO"), true);
  }

  /** @since NeXtMidas 3.1.2 */
  public void testExpandVariables () {
    final HashMap<String, String> map = new HashMap<>();
    map.put("x", "quick");
    map.put("y", "brown");
    map.put("z", "fox");
    map.put("a", "z");
    final String text = "The $x ${y} ${$a} jumped over $$a ; $non_existent_key $a. ${missing_closing_brace ${a}.";
    String expected = "The quick brown fox jumped over $a ; $non_existent_key $a. ${missing_closing_brace z.";

    String result = StringUtil.expandVariables(text, map, StringUtil.NO_ERROR);
    assertEquals("expandVariables - NO_ERROR", expected, result);

    assertException("expandVariables - Error expected", MidasException.class,
      new Runnable() { public void run() { StringUtil.expandVariables(text, map, 0); } }
    );

    String expected2 = "The quick brown fox.";
    String result2 = StringUtil.expandVariables("The ${x} $y ${z}.", map, 0);
    assertEquals("expandVariables - no Error", expected2, result2);
  }

  /** A simple Keyable implementation that doesn't extend Table or KeyVector. */
  private static class MyKeyable implements Keyable {
    Table table = new Table(Table.HT);

    public Object   setKey(String key,Object val) { return table.setKey(key,val); }
    public Object   getKey(String key)            { return table.getKey(key);     }
    public String[] getKeys()                     { return table.getKeys();       }
  }

  /** Tests to see if one of the set matches. */
  private boolean matchesOne (Object obj, Object[] set) {
    int matches = 0;
    for (Object valInSet : set) {
      if (obj.equals(valInSet)) { matches++; }
    }
    return (matches == 1);
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////
  private void testIsNull (String[] list, boolean expected) {
    for (String str : list) {
                       assertEquals("isNull("+str+")",               expected, StringUtil.isNull(str));
      if (str!=null) { assertEquals("isNull("+str.toLowerCase()+")", expected, StringUtil.isNull(str.toLowerCase())); }
      if (str!=null) { assertEquals("isNull("+str.toUpperCase()+")", expected, StringUtil.isNull(str.toUpperCase())); }
    }
  }

  private void testIsNullOrEmpty (Object[] list, boolean expected) { // since 3.3.1
    for (Object obj : list) {
      assertEquals("isNullOrEmpty("+obj+")", expected, StringUtil.isNullOrEmpty(obj));
    }
  }

  private void testIsEmpty (CharSequence[] list, boolean expected) { // since 3.3.1
    for (CharSequence charSeq : list) {
      assertEquals("isEmpty("+charSeq+")", expected, StringUtil.isEmpty(charSeq));
    }
  }

  private void testIsTrue (String[] list, boolean expected) {
    for (String str : list) {
                       assertEquals("isTrue("+str+")",               expected, StringUtil.isTrue(str));
      if (str!=null) { assertEquals("isTrue("+str.toLowerCase()+")", expected, StringUtil.isTrue(str.toLowerCase())); }
      if (str!=null) { assertEquals("isTrue("+str.toUpperCase()+")", expected, StringUtil.isTrue(str.toUpperCase())); }
    }
  }

  private void testIsFalse (String[] list, boolean expected) {
    for (String str : list) {
                       assertEquals("isFalse("+str+")",               expected, StringUtil.isFalse(str));
      if (str!=null) { assertEquals("isFalse("+str.toLowerCase()+")", expected, StringUtil.isFalse(str.toLowerCase())); }
      if (str!=null) { assertEquals("isFalse("+str.toUpperCase()+")", expected, StringUtil.isFalse(str.toUpperCase())); }
    }
  }

  private void testIsNumber (String[] list, boolean expected) {
    for (String str : list) {
                       assertEquals("isNumber("+str+")",               expected, StringUtil.isNumber(str));
      if (str!=null) { assertEquals("isNumber("+str.toLowerCase()+")", expected, StringUtil.isNumber(str.toLowerCase())); }
      if (str!=null) { assertEquals("isNumber("+str.toUpperCase()+")", expected, StringUtil.isNumber(str.toUpperCase())); }
    }
  }

  private void testIsInteger (String[] list, boolean expected) {
    for (String str : list) {
                       assertEquals("isInteger("+str+")",               expected, StringUtil.isInteger(str));
      if (str!=null) { assertEquals("isInteger("+str.toLowerCase()+")", expected, StringUtil.isInteger(str.toLowerCase())); }
      if (str!=null) { assertEquals("isInteger("+str.toUpperCase()+")", expected, StringUtil.isInteger(str.toUpperCase())); }
    }
  }

  private void testIsHexInteger (String[] list, boolean expected) {
    for (String str : list) {
                       assertEquals("isHexInteger("+str+")",               expected, StringUtil.isHexInteger(str));
      if (str!=null) { assertEquals("isHexInteger("+str.toLowerCase()+")", expected, StringUtil.isHexInteger(str.toLowerCase())); }
      if (str!=null) { assertEquals("isHexInteger("+str.toUpperCase()+")", expected, StringUtil.isHexInteger(str.toUpperCase())); }
    }
  }

  private void testIsFloat (String[] list, boolean expected) {
    for (String str : list) {
                       assertEquals("isFloat("+str+")",               expected, StringUtil.isFloat(str));
      if (str!=null) { assertEquals("isFloat("+str.toLowerCase()+")", expected, StringUtil.isFloat(str.toLowerCase())); }
      if (str!=null) { assertEquals("isFloat("+str.toUpperCase()+")", expected, StringUtil.isFloat(str.toUpperCase())); }
    }
  }

  private void testIsNaN (String[] list, boolean expected) {
    for (String str : list) {
                       assertEquals("isNaN("+str+")",               expected, StringUtil.isNaN(str));
      if (str!=null) { assertEquals("isNaN("+str.toLowerCase()+")", expected, StringUtil.isNaN(str.toLowerCase())); }
      if (str!=null) { assertEquals("isNaN("+str.toUpperCase()+")", expected, StringUtil.isNaN(str.toUpperCase())); }
    }
  }

  private void testIsInf (String[] list, boolean expected) {
    for (String str : list) {
                       assertEquals("isInf("+str+")",               expected, StringUtil.isInf(str));
      if (str!=null) { assertEquals("isInf("+str.toLowerCase()+")", expected, StringUtil.isInf(str.toLowerCase())); }
      if (str!=null) { assertEquals("isInf("+str.toUpperCase()+")", expected, StringUtil.isInf(str.toUpperCase())); }
    }
  }

  public void testNAME_COMPARATOR () {
    Object[] TESTS = {
      // A                           B                          RESULT (-1,0,+1)
      "ABC",                       "XYZ",                       -1,
      "123",                       "456",                       -1,
      "X007",                      "X7",                         0, // "X"="X" and 007 = 7
      "ABC9",                      "ABC10",                     -1,
      "A9B10",                     "A10B9",                     -1,
      "ABC",                       "ABC0",                      -1,
      123,                         456,                         -1, // Integer
      new StringBuilder("ABC9"),   new StringBuilder("ABC10"),  -1, // StringBuilder
    };

    for (int i = 0; i < TESTS.length; i+=3) {
      int act1 = StringUtil.NAME_COMPARATOR.compare(TESTS[i], TESTS[i+1]);
      int exp1 = (Integer)TESTS[i+2];
      int act2 = StringUtil.NAME_COMPARATOR.compare(TESTS[i+1], TESTS[i]);
      int exp2 = -exp1;


      if (act1 < 0) act1 = -1;
      if (act1 > 0) act1 = +1;

      if (act2 < 0) act2 = -1;
      if (act2 > 0) act2 = +1;

      assertEquals("NAME_COMPARATOR with '"+TESTS[i]+"' and '"+TESTS[i+1]+"'", exp1, act1);
      assertEquals("NAME_COMPARATOR with '"+TESTS[i+1]+"' and '"+TESTS[i]+"'", exp2, act2);
    }
  }

  @Test
  public void testToTitleCase() {
    String expectedValue = "This Should Be Title Cased";
    String expectedValue2 = "Should Mid Word Letters Be Capitalized Still In Title Case NeXtMidas";
    String testValue = "this should be title cased";
    String testValue2 = "This Should Be Title Cased";
    String testValue3 = "should mid word letters be capitalized still in title case neXtMidas";

    assertEquals(expectedValue, StringUtil.toTitleCase(testValue));
    assertEquals(expectedValue, StringUtil.toTitleCase(testValue2));
    assertEquals("The capital X should be maintained mid word", expectedValue2, StringUtil.toTitleCase(testValue3));
  }

  @Test
  public void testAsStrList() {
    String[] expectedValues = {"This","is","a","test"};
    // comma separated list string.
    String values = "[This,is,a,test]";
    // comma separated list string
    String values2 = "This,is,a,test";
    // Created Linked List for conversion
    LinkedList<String> values3 = new LinkedList<>(Arrays.asList(expectedValues)); // Create a linkedlist from the expected results
    // Create Queue for conversion
    Queue<String> values4 = new ArrayDeque<>(Arrays.asList(expectedValues));
    // Create stack for conversion
    Stack<String> values5 = new Stack<>();
    for(int i = 0;i<expectedValues.length;i++) {
      values5.push(expectedValues[i]);
    }

    List<String> actual= StringUtil.asStrList(values);
    List<String> actual2= StringUtil.asStrList(values2);
    List<String> actual3= StringUtil.asStrList(values3);
    List<String> actual4= StringUtil.asStrList(values4);
    List<String> actual5= StringUtil.asStrList(values5);

    helpTestAsStrList(actual, expectedValues);
    helpTestAsStrList(actual2, expectedValues);
    helpTestAsStrList(actual3, expectedValues);
    helpTestAsStrList(actual4, expectedValues);
    helpTestAsStrList(actual5, expectedValues);

    // Test empty string as empty list
    assertTrue(StringUtil.asStrList("").isEmpty());
    assertTrue(StringUtil.asStrList("[]").isEmpty());
    assertTrue(StringUtil.asStrList(new LinkedList<String>()).isEmpty());
    assertTrue(StringUtil.asStrList(new ArrayDeque<String>()).isEmpty());
    assertTrue(StringUtil.asStrList(new Stack<String>()).isEmpty());
    assertTrue(StringUtil.asStrList(null).isEmpty());
    assertTrue(StringUtil.asStrList(actual).equals(actual));
  }

  private void helpTestAsStrList(List<String> actual, String[] expectedValues) {
    assertEquals(expectedValues[0],actual.get(0));
    assertEquals(expectedValues[1],actual.get(1));
    assertEquals(expectedValues[2],actual.get(2));
    assertEquals(expectedValues[3],actual.get(3));
  }

  @Test
  public void testAsStrSet() {
    String[] expectedValues = {"This","is","a","test"};
    Arrays.sort(expectedValues);
    String values = "[This,is,a,test]";
    String values2 = "This,is,a,test";
    Set<String> actual1 = StringUtil.asStrSet(values);
    Set<String> actual2 = StringUtil.asStrSet(values2);
    int i = 0;
    for(String s : actual1) {
      assertEquals(expectedValues[i++], s);
    }
    i=0;
    for(String s : actual2) {
      assertEquals(expectedValues[i++], s);
    }
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////
  private static final class TestFense {
    final String string;
    final int    opening;
    final int    closing;
    final String fense;

    public TestFense (String string, int opening, int closing, String fense) {
      this.string  = string;
      this.opening = opening;
      this.closing = closing;
      this.fense   = fense;
    }
  }
}
