Merge develop to Release_2_8_3_Branch
[jalview.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
99   /**
100    * This method replaces the old search results which merely held an alignment
101    * index of search matches. This broke when sequences were moved around the
102    * alignment
103    * 
104    * @param seq
105    *          Sequence
106    * @param start
107    *          int
108    * @param end
109    *          int
110    */
111   public void addResult(SequenceI seq, int start, int end)
112   {
113     matches.add(new Match(seq, start, end));
114   }
115
116   /**
117    * Quickly check if the given sequence is referred to in the search results
118    * 
119    * @param sequence
120    *          (specific alignment sequence or a dataset sequence)
121    * @return true if the results involve sequence
122    */
123   public boolean involvesSequence(SequenceI sequence)
124   {
125     SequenceI ds = sequence.getDatasetSequence();
126     for (Match m : matches)
127     {
128       if (m.sequence != null
129               && (m.sequence == sequence || m.sequence == ds))
130       {
131         return true;
132       }
133     }
134     return false;
135   }
136
137   /**
138    * This Method returns the search matches which lie between the start and end
139    * points of the sequence in question. It is optimised for returning objects
140    * for drawing on SequenceCanvas
141    */
142   public int[] getResults(SequenceI sequence, int start, int end)
143   {
144     if (matches.isEmpty())
145     {
146       return null;
147     }
148
149     int[] result = null;
150     int[] tmp = null;
151     int resultLength, matchStart = 0, matchEnd = 0;
152     boolean mfound;
153     for (Match m : matches)
154     {
155       mfound = false;
156       if (m.sequence == sequence)
157       {
158         mfound = true;
159         // locate aligned position
160         matchStart = sequence.findIndex(m.start) - 1;
161         matchEnd = sequence.findIndex(m.end) - 1;
162       }
163       else if (m.sequence == sequence.getDatasetSequence())
164       {
165         mfound = true;
166         // locate region in local context
167         matchStart = sequence.findIndex(m.start) - 1;
168         matchEnd = sequence.findIndex(m.end) - 1;
169       }
170       if (mfound)
171       {
172         if (matchStart <= end && matchEnd >= start)
173         {
174           if (matchStart < start)
175           {
176             matchStart = start;
177           }
178
179           if (matchEnd > end)
180           {
181             matchEnd = end;
182           }
183
184           if (result == null)
185           {
186             result = new int[]
187             { matchStart, matchEnd };
188           }
189           else
190           {
191             resultLength = result.length;
192             tmp = new int[resultLength + 2];
193             System.arraycopy(result, 0, tmp, 0, resultLength);
194             result = tmp;
195             result[resultLength] = matchStart;
196             result[resultLength + 1] = matchEnd;
197           }
198         }
199         else
200         {
201           // debug
202           // System.err.println("Outwith bounds!" + matchStart+">"+end +"  or "
203           // + matchEnd+"<"+start);
204         }
205       }
206     }
207     return result;
208   }
209
210   public int getSize()
211   {
212     return matches.size();
213   }
214
215   public SequenceI getResultSequence(int index)
216   {
217     return matches.get(index).sequence;
218   }
219
220   /**
221    * Returns the start position of the i'th match in the search results.
222    * 
223    * @param i
224    * @return
225    */
226   public int getResultStart(int i)
227   {
228     return matches.get(i).start;
229   }
230
231   /**
232    * Returns the end position of the i'th match in the search results.
233    * 
234    * @param i
235    * @return
236    */
237   public int getResultEnd(int i)
238   {
239     return matches.get(i).end;
240   }
241
242   /**
243    * Returns true if no search result matches are held.
244    * 
245    * @return
246    */
247   public boolean isEmpty()
248   {
249     return matches.isEmpty();
250   }
251
252   /**
253    * Returns the list of matches.
254    * 
255    * @return
256    */
257   public List<Match> getResults()
258   {
259     return matches;
260   }
261
262   /**
263    * Return the results as a string of characters. Meant for use when the
264    * context ensures that all matches are to regions of the same sequence
265    * (otherwise the result is meaningless).
266    * 
267    * @return
268    */
269   @Override
270   public String toString()
271   {
272     StringBuilder result = new StringBuilder(256);
273     for (Match m : matches)
274     {
275       result.append(m.toString());
276     }
277     return result.toString();
278   }
279 }