JAL-1807 Bob's JalviewJS prototype first commit
[jalviewjs.git] / src / jalview / datamodel / SearchResults.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.datamodel;
22
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.List;
26
27 /**
28  * Holds a list of search result matches, where each match is a contiguous
29  * stretch of a single sequence.
30  * 
31  * @author gmcarstairs
32  *
33  */
34 public class SearchResults
35 {
36
37   private List<Match> matches = new ArrayList<Match>();
38
39   public class Match
40   {
41     SequenceI sequence;
42
43     /**
44      * Start position of match in sequence (base 1)
45      */
46     int start;
47
48     /**
49      * End position (inclusive) (base 1)
50      */
51     int end;
52
53     /**
54      * Constructor
55      * 
56      * @param seq
57      *          a sequence
58      * @param start
59      *          start position of matched range (base 1)
60      * @param end
61      *          end of matched range (inclusive, base 1)
62      */
63     public Match(SequenceI seq, int start, int end)
64     {
65       sequence = seq;
66       this.start = start;
67       this.end = end;
68     }
69
70     public SequenceI getSequence()
71     {
72       return sequence;
73     }
74
75     public int getStart()
76     {
77       return start;
78     }
79
80     public int getEnd()
81     {
82       return end;
83     }
84
85     /**
86      * Returns the string of characters in the matched region.
87      */
88     @Override
89     public String toString()
90     {
91       char[] chars = sequence.getSequence();
92       // convert start/end to base 0 (with bounds check)
93       final int from = Math.max(start - 1, 0);
94       final int to = Math.min(end, chars.length + 1);
95       return String.valueOf(Arrays.copyOfRange(chars, from, to));
96     }
97
98     public void setSequence(SequenceI seq)
99     {
100       this.sequence = seq;
101     }
102   }
103
104   /**
105    * This method replaces the old search results which merely held an alignment
106    * index of search matches. This broke when sequences were moved around the
107    * alignment
108    * 
109    * @param seq
110    *          Sequence
111    * @param start
112    *          int
113    * @param end
114    *          int
115    */
116   public void addResult(SequenceI seq, int start, int end)
117   {
118     matches.add(new Match(seq, start, end));
119   }
120
121   /**
122    * Quickly check if the given sequence is referred to in the search results
123    * 
124    * @param sequence
125    *          (specific alignment sequence or a dataset sequence)
126    * @return true if the results involve sequence
127    */
128   public boolean involvesSequence(SequenceI sequence)
129   {
130     SequenceI ds = sequence.getDatasetSequence();
131     for (Match m : matches)
132     {
133       if (m.sequence != null
134               && (m.sequence == sequence || m.sequence == ds))
135       {
136         return true;
137       }
138     }
139     return false;
140   }
141
142   /**
143    * This Method returns the search matches which lie between the start and end
144    * points of the sequence in question. It is optimised for returning objects
145    * for drawing on SequenceCanvas
146    */
147   public int[] getResults(SequenceI sequence, int start, int end)
148   {
149     if (matches.isEmpty())
150     {
151       return null;
152     }
153
154     int[] result = null;
155     int[] tmp = null;
156     int resultLength, matchStart = 0, matchEnd = 0;
157     boolean mfound;
158     for (Match m : matches)
159     {
160       mfound = false;
161       if (m.sequence == sequence)
162       {
163         mfound = true;
164         // locate aligned position
165         matchStart = sequence.findIndex(m.start) - 1;
166         matchEnd = sequence.findIndex(m.end) - 1;
167       }
168       else if (m.sequence == sequence.getDatasetSequence())
169       {
170         mfound = true;
171         // locate region in local context
172         matchStart = sequence.findIndex(m.start) - 1;
173         matchEnd = sequence.findIndex(m.end) - 1;
174       }
175       if (mfound)
176       {
177         if (matchStart <= end && matchEnd >= start)
178         {
179           if (matchStart < start)
180           {
181             matchStart = start;
182           }
183
184           if (matchEnd > end)
185           {
186             matchEnd = end;
187           }
188
189           if (result == null)
190           {
191             result = new int[]
192             { matchStart, matchEnd };
193           }
194           else
195           {
196             resultLength = result.length;
197             tmp = new int[resultLength + 2];
198             System.arraycopy(result, 0, tmp, 0, resultLength);
199             result = tmp;
200             result[resultLength] = matchStart;
201             result[resultLength + 1] = matchEnd;
202           }
203         }
204         else
205         {
206           // debug
207           // System.err.println("Outwith bounds!" + matchStart+">"+end +"  or "
208           // + matchEnd+"<"+start);
209         }
210       }
211     }
212     return result;
213   }
214
215   public int getSize()
216   {
217     return matches.size();
218   }
219
220   public SequenceI getResultSequence(int index)
221   {
222     return matches.get(index).sequence;
223   }
224
225   /**
226    * Returns the start position of the i'th match in the search results.
227    * 
228    * @param i
229    * @return
230    */
231   public int getResultStart(int i)
232   {
233     return matches.get(i).start;
234   }
235
236   /**
237    * Returns the end position of the i'th match in the search results.
238    * 
239    * @param i
240    * @return
241    */
242   public int getResultEnd(int i)
243   {
244     return matches.get(i).end;
245   }
246
247   /**
248    * Returns true if no search result matches are held.
249    * 
250    * @return
251    */
252   public boolean isEmpty()
253   {
254     return matches.isEmpty();
255   }
256
257   /**
258    * Returns the list of matches.
259    * 
260    * @return
261    */
262   public List<Match> getResults()
263   {
264     return matches;
265   }
266
267   /**
268    * Return the results as a string of characters. Meant for use when the
269    * context ensures that all matches are to regions of the same sequence
270    * (otherwise the result is meaningless).
271    * 
272    * @return
273    */
274   @Override
275   public String toString()
276   {
277     StringBuilder result = new StringBuilder(256);
278     for (Match m : matches)
279     {
280       result.append(m.toString());
281     }
282     return result.toString();
283   }
284 }