JAL-4059 simpler reimplementation of StringUtils.separatorListToArray to avoid unnece...
authorBen Soares <b.soares@dundee.ac.uk>
Tue, 31 Oct 2023 12:57:25 +0000 (12:57 +0000)
committerBen Soares <b.soares@dundee.ac.uk>
Tue, 31 Oct 2023 12:57:25 +0000 (12:57 +0000)
src/jalview/util/StringUtils.java
test/jalview/util/StringUtilsTest.java

index 9212c8e..1b2cbaf 100644 (file)
@@ -144,6 +144,7 @@ public class StringUtils
    * Parses the input string into components separated by the delimiter. Unlike
    * String.split(), this method will ignore occurrences of the delimiter which
    * are nested within single quotes in name-value pair values, e.g. a='b,c'.
+   * New implementation to avoid Pattern for jalviewjs.
    * 
    * @param input
    * @param delimiter
@@ -152,72 +153,54 @@ public class StringUtils
   public static String[] separatorListToArray(String input,
           String delimiter)
   {
-    int seplen = delimiter.length();
-    if (input == null || input.equals("") || input.equals(delimiter))
+    if (input == null
+            // these two shouldn't return null (one or two "" respectively)
+            || input.equals("") || input.equals(delimiter))
     {
       return null;
     }
-    List<String> jv = new ArrayList<>();
-    int cp = 0, pos, escape;
-    boolean wasescaped = false, wasquoted = false;
-    String lstitem = null;
-    while ((pos = input.indexOf(delimiter, cp)) >= cp)
+
+    final char escapeChar = '\\';
+    final char quoteChar = '\'';
+    int ilength = input.length();
+    int dlength = delimiter.length();
+    List<String> values = new ArrayList<>();
+
+    boolean escape = false;
+    boolean inquote = false;
+
+    int start = 0;
+    for (int i = 0; i < ilength; i++)
     {
-      escape = (pos > 0 && input.charAt(pos - 1) == '\\') ? -1 : 0;
-      if (wasescaped || wasquoted)
+      if (!escape && !inquote && ilength >= i + dlength
+              && input.substring(i, i + dlength).equals(delimiter))
       {
-        // append to previous pos
-        jv.set(jv.size() - 1, lstitem = lstitem + delimiter
-                + input.substring(cp, pos + escape));
+        // found a delimiter
+        values.add(input.substring(start, i));
+        i += dlength;
+        start = i;
+        continue;
       }
-      else
+      char c = input.charAt(i);
+      if (c == escapeChar)
       {
-        jv.add(lstitem = input.substring(cp, pos + escape));
-      }
-      cp = pos + seplen;
-      wasescaped = escape == -1;
-      // last separator may be in an unmatched quote
-      // private static final Pattern DELIMITERS_PATTERN =
-      // Pattern.compile(".*='[^']*(?!')");
-      wasquoted = DELIMITERS_PATTERN.matcher(lstitem).matches();
-    }
-    if (cp < input.length())
-    {
-      String c = input.substring(cp);
-      if (wasescaped || wasquoted)
-      {
-        // append final separator
-        jv.set(jv.size() - 1, lstitem + delimiter + c);
+        escape = !escape;
+        continue;
       }
-      else
+      if (escape)
       {
-        if (!c.equals(delimiter))
-        {
-          jv.add(c);
-        }
+        escape = false;
+        continue;
       }
-    }
-    if (jv.size() > 0)
-    {
-      String[] v = jv.toArray(new String[jv.size()]);
-      jv.clear();
-      if (DEBUG)
+      if (c == quoteChar)
       {
-        jalview.bin.Console.errPrintln("Array from '" + delimiter
-                + "' separated List:\n" + v.length);
-        for (int i = 0; i < v.length; i++)
-        {
-          jalview.bin.Console.errPrintln("item " + i + " '" + v[i] + "'");
-        }
+        inquote = !inquote;
       }
-      return v;
     }
-    if (DEBUG)
-    {
-      jalview.bin.Console.errPrintln(
-              "Empty Array from '" + delimiter + "' separated List");
-    }
-    return null;
+    // add the last value
+    values.add(input.substring(start, ilength));
+
+    return values.toArray(new String[values.size()]);
   }
 
   /**
index 22324fc..dc7ed92 100644 (file)
@@ -24,8 +24,6 @@ import static org.testng.AssertJUnit.assertEquals;
 import static org.testng.AssertJUnit.assertNull;
 import static org.testng.AssertJUnit.assertTrue;
 
-import jalview.gui.JvOptionPane;
-
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -33,6 +31,8 @@ import java.util.List;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
+import jalview.gui.JvOptionPane;
+
 public class StringUtilsTest
 {
 
@@ -121,6 +121,16 @@ public class StringUtilsTest
      */
     assertEquals("[abc='|'d, ef, g]", Arrays.toString(
             StringUtils.separatorListToArray("abc='|'d|ef|g", "|")));
+
+    /*
+     * edge cases
+     */
+    assertEquals("[abc=';'d, ef, g, key='val;ue;key2=what]",
+            Arrays.toString(StringUtils.separatorListToArray(
+                    "abc=';'d;ef;g;key='val;ue;key2=what", ";")));
+    assertEquals("[hello\\world, jello'\\world', mello'wo\\'rld']",
+            Arrays.toString(StringUtils.separatorListToArray(
+                    "hello\\world;jello'\\world';mello'wo\\'rld'", ";")));
   }
 
   @Test(groups = { "Functional" })