Merge branch 'develop' into features/JAL-2094_colourInterface
[jalview.git] / src / jalview / util / DnaUtils.java
diff --git a/src/jalview/util/DnaUtils.java b/src/jalview/util/DnaUtils.java
new file mode 100644 (file)
index 0000000..9582e2e
--- /dev/null
@@ -0,0 +1,133 @@
+package jalview.util;
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class DnaUtils
+{
+
+  /**
+   * Parses an ENA/GenBank format location specifier and returns a list of
+   * [start, end] ranges. Throws an exception if not able to parse.
+   * <p>
+   * Currently we do not parse "order()" specifiers, or indeterminate ranges of
+   * the format "&lt;start..end" or "start..&gt;end" or "start.end" or
+   * "start^end"
+   * 
+   * @param location
+   * @return
+   * @throws ParseException
+   *           if unable to parse the location (the exception message is the
+   *           location specifier being parsed); we use ParseException in
+   *           preference to the unchecked IllegalArgumentException
+   * @see http://www.insdc.org/files/feature_table.html#3.4
+   */
+  public static List<int[]> parseLocation(String location)
+          throws ParseException
+  {
+    if (location.startsWith("join("))
+    {
+      return parseJoin(location);
+    }
+    else if (location.startsWith("complement("))
+    {
+      return parseComplement(location);
+    }
+    if (location.startsWith("order("))
+    {
+      throw new ParseException(location, 0);
+    }
+
+    /*
+     * try to parse m..n (or simply m)
+     */
+    String[] range = location.split("\\.\\.");
+    if (range.length == 1 || range.length == 2)
+    {
+      try
+      {
+        int start = Integer.valueOf(range[0]);
+        int end = range.length == 1 ? start : Integer.valueOf(range[1]);
+        return Collections.singletonList(new int[] { start, end });
+      } catch (NumberFormatException e)
+      {
+        /*
+         * could be a location like <1..888 or 1..>888
+         */
+        throw new ParseException(location, 0);
+      }
+    }
+    else
+    {
+      /*
+       * could be a location like 102.110 or 123^124
+       */
+      throw new ParseException(location, 0);
+    }
+  }
+
+  /**
+   * Parses a complement(locationSpec) into a list of start-end ranges
+   * 
+   * @param location
+   * @return
+   * @throws ParseException
+   */
+  static List<int[]> parseComplement(String location) throws ParseException
+  {
+    /*
+     * take what is inside complement()
+     */
+    if (!location.endsWith(")"))
+    {
+      throw new ParseException(location, 0);
+    }
+    String toComplement = location.substring("complement(".length(),
+            location.length() - 1);
+    List<int[]> ranges = parseLocation(toComplement);
+
+    /*
+     * reverse the order and direction of ranges
+     */
+    Collections.reverse(ranges);
+    for (int[] range : ranges)
+    {
+      int temp = range[0];
+      range[0] = range[1];
+      range[1] = temp;
+    }
+    return ranges;
+  }
+
+  /**
+   * Parses a join(loc1,loc2,...,locn) into a list of start-end ranges
+   * 
+   * @param location
+   * @return
+   * @throws ParseException
+   */
+  static List<int[]> parseJoin(String location) throws ParseException
+  {
+    List<int[]> ranges = new ArrayList<int[]>();
+
+    /*
+     * take what is inside join()
+     */
+    if (!location.endsWith(")"))
+    {
+      throw new ParseException(location, 0);
+    }
+    String joinedLocs = location.substring("join(".length(),
+            location.length() - 1);
+    String[] locations = joinedLocs.split(",");
+    for (String loc : locations)
+    {
+      List<int[]> range = parseLocation(loc);
+      ranges.addAll(range);
+    }
+    return ranges;
+  }
+
+}