JAL-2114 accept "<123..178" or "123..>178" format
[jalview.git] / src / jalview / util / DnaUtils.java
1 package jalview.util;
2
3 import java.util.ArrayList;
4 import java.util.Collections;
5 import java.util.List;
6
7 public class DnaUtils
8 {
9
10   /**
11    * Parses an ENA/GenBank format location specifier and returns a list of
12    * [start, end] ranges. Returns null if not able to parse.
13    * 
14    * @param location
15    * @return
16    * @see http://www.insdc.org/files/feature_table.html#3.4
17    */
18   public static List<int[]> parseLocation(String location)
19   {
20     if (location.startsWith("join("))
21     {
22       return parseJoin(location);
23     }
24     else if (location.startsWith("complement("))
25     {
26       return parseComplement(location);
27     }
28     String errorMessage = "Unable to process location specifier: "
29             + location;
30     if (location.startsWith("order("))
31     {
32       System.err.println(errorMessage);
33       return null;
34     }
35
36     /*
37      * try to parse m..n (or simply m)
38      * also handles <m..n or m..>n (discarding < or >)
39      */
40     String[] range = location.split("\\.\\.");
41     if (range.length == 1 || range.length == 2)
42     {
43       try
44       {
45         int start = parseRangeEnd(range[0]);
46         int end = range.length == 1 ? start : parseRangeEnd(range[1]);
47         return Collections.singletonList(new int[] { start, end });
48       } catch (NumberFormatException e)
49       {
50         /*
51          * could be a location like <1..888 or 1..>888
52          */
53         System.err.println(errorMessage);
54         return null;
55       }
56     }
57     else
58     {
59       /*
60        * could be a location like 102.110 or 123^124
61        */
62       System.err.println(errorMessage);
63       return null;
64     }
65   }
66
67   /**
68    * Returns the integer value of a locus, discarding any < or > prefix
69    * 
70    * @throws NumberFormatException
71    *           if value is not numeric
72    */
73   static int parseRangeEnd(String loc)
74   {
75
76     if (loc.startsWith("<") || loc.startsWith(">"))
77     {
78       loc = loc.substring(1);
79     }
80     return Integer.valueOf(loc);
81   }
82
83   /**
84    * Parses a complement(locationSpec) into a list of start-end ranges
85    * 
86    * @param location
87    * @return
88    */
89   static List<int[]> parseComplement(String location)
90   {
91     /*
92      * take what is inside complement()
93      */
94     if (!location.endsWith(")"))
95     {
96       return null;
97     }
98     String toComplement = location.substring("complement(".length(),
99             location.length() - 1);
100     List<int[]> ranges = parseLocation(toComplement);
101     if (ranges == null)
102     {
103       /*
104        * something bad in there
105        */
106       return null;
107     }
108
109     /*
110      * reverse the order and direction of ranges
111      */
112     Collections.reverse(ranges);
113     for (int[] range : ranges)
114     {
115       int temp = range[0];
116       range[0] = range[1];
117       range[1] = temp;
118     }
119     return ranges;
120   }
121
122   /**
123    * Parses a join(loc1,loc2,...,locn) into a list of start-end ranges
124    * 
125    * @param location
126    * @return
127    */
128   static List<int[]> parseJoin(String location)
129   {
130     List<int[]> ranges = new ArrayList<int[]>();
131
132     /*
133      * take what is inside join()
134      */
135     if (!location.endsWith(")"))
136     {
137       return null;
138     }
139     String joinedLocs = location.substring("join(".length(),
140             location.length() - 1);
141     String[] locations = joinedLocs.split(",");
142     for (String loc : locations)
143     {
144       List<int[]> range = parseLocation(loc);
145       if (range == null)
146       {
147         /*
148          * something bad in there
149          */
150         return null;
151       }
152       else
153       {
154         ranges.addAll(range);
155       }
156     }
157     return ranges;
158   }
159
160 }