JAL-2664 Updates following review
[jalview.git] / src / jalview / analysis / Finder.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.analysis;
22
23 import jalview.datamodel.AlignmentI;
24 import jalview.datamodel.SearchResultMatchI;
25 import jalview.datamodel.SearchResults;
26 import jalview.datamodel.SearchResultsI;
27 import jalview.datamodel.SequenceGroup;
28 import jalview.datamodel.SequenceI;
29 import jalview.util.Comparison;
30
31 import java.util.ArrayList;
32 import java.util.List;
33 import java.util.Vector;
34
35 import com.stevesoft.pat.Regex;
36
37 public class Finder
38 {
39   /**
40    * Implements the search algorithms for the Find dialog box.
41    */
42   SearchResultsI searchResults;
43
44   AlignmentI alignment;
45
46   SequenceGroup selection = null;
47
48   Vector<SequenceI> idMatch = null;
49
50   boolean caseSensitive = false;
51
52   private boolean includeDescription = false;
53
54   boolean findAll = false;
55
56   Regex regex = null;
57
58   /**
59    * holds last-searched position between calls to find(false)
60    */
61   int seqIndex = 0, resIndex = -1;
62
63   public Finder(AlignmentI alignment, SequenceGroup selection)
64   {
65     this.alignment = alignment;
66     this.selection = selection;
67   }
68
69   /**
70    * restart search at given sequence and residue on alignment and (optionally)
71    * contained in selection
72    * 
73    * @param alignment
74    * @param selectionGroup
75    * @param seqIndex
76    * @param resIndex
77    */
78   public Finder(AlignmentI alignment, SequenceGroup selectionGroup,
79           int seqIndex, int resIndex)
80   {
81     this(alignment, selectionGroup);
82     this.seqIndex = seqIndex;
83     this.resIndex = resIndex;
84   }
85
86   public boolean find(String searchString)
87   {
88     boolean hasResults = false;
89     if (!caseSensitive)
90     {
91       searchString = searchString.toUpperCase();
92     }
93     regex = new Regex(searchString);
94     regex.setIgnoreCase(!caseSensitive);
95     searchResults = new SearchResults();
96     idMatch = new Vector<SequenceI>();
97     String item = null;
98     boolean found = false;
99     int end = alignment.getHeight();
100
101     // /////////////////////////////////////////////
102
103     if (selection != null)
104     {
105       if ((selection.getSize() < 1)
106               || ((selection.getEndRes() - selection.getStartRes()) < 2))
107       {
108         selection = null;
109       }
110     }
111     SearchResultMatchI lastm = null;
112
113     while (!found && (seqIndex < end))
114     {
115       SequenceI seq = alignment.getSequenceAt(seqIndex);
116
117       if ((selection != null && selection.getSize() > 0)
118               && !selection.getSequences(null).contains(seq))
119       {
120         seqIndex++;
121         resIndex = -1;
122
123         continue;
124       }
125       if (resIndex < 0)
126       {
127         resIndex = 0;
128         // test for one off matches - sequence position and sequence ID
129         // //// is the searchString a residue number?
130         try
131         {
132           int res = Integer.parseInt(searchString);
133           // possibly a residue number - check if valid for seq
134           if (seq.getEnd() >= res)
135           {
136             searchResults.addResult(seq, res, res);
137             hasResults = true;
138             // resIndex=seq.getLength();
139             // seqIndex++;
140             if (!findAll)
141             {
142               found = true;
143               break;
144             }
145           }
146         } catch (NumberFormatException ex)
147         {
148         }
149
150         if (regex.search(seq.getName()) && !idMatch.contains(seq))
151         {
152           idMatch.addElement(seq);
153           hasResults = true;
154           if (!findAll)
155           {
156             // stop and return the match
157             found = true;
158             break;
159           }
160         }
161
162         if (isIncludeDescription() && seq.getDescription() != null
163                 && regex.search(seq.getDescription())
164                 && !idMatch.contains(seq))
165         {
166           idMatch.addElement(seq);
167           hasResults = true;
168           if (!findAll)
169           {
170             // stop and return the match
171             found = true;
172             break;
173           }
174         }
175       }
176       item = seq.getSequenceAsString();
177
178       if ((selection != null)
179               && (selection.getEndRes() < alignment.getWidth() - 1))
180       {
181         item = item.substring(0, selection.getEndRes() + 1);
182       }
183
184       // /Shall we ignore gaps???? - JBPNote: Add Flag for forcing this or not
185       StringBuilder noGapsSB = new StringBuilder();
186       int insertCount = 0;
187       List<Integer> spaces = new ArrayList<Integer>();
188
189       for (int j = 0; j < item.length(); j++)
190       {
191         if (!Comparison.isGap(item.charAt(j)))
192         {
193           noGapsSB.append(item.charAt(j));
194           spaces.add(Integer.valueOf(insertCount));
195         }
196         else
197         {
198           insertCount++;
199         }
200       }
201
202       String noGaps = noGapsSB.toString();
203       for (int r = resIndex; r < noGaps.length(); r++)
204       {
205
206         if (regex.searchFrom(noGaps, r))
207         {
208           resIndex = regex.matchedFrom();
209
210           if ((selection != null && selection.getSize() > 0)
211                   && (resIndex + spaces.get(resIndex) < selection
212                           .getStartRes()))
213           {
214             continue;
215           }
216           // if invalid string used, then regex has no matched to/from
217           int sres = seq.findPosition(resIndex + spaces.get(resIndex));
218           int eres = seq.findPosition(regex.matchedTo() - 1
219                   + (spaces.get(regex.matchedTo() - 1)));
220           // only add result if not contained in previous result
221           if (lastm == null
222                   || (lastm.getSequence() != seq || (!(lastm.getStart() <= sres && lastm
223                           .getEnd() >= eres))))
224           {
225             lastm = searchResults.addResult(seq, sres, eres);
226           }
227           hasResults = true;
228           if (!findAll)
229           {
230             // thats enough, break and display the result
231             found = true;
232             resIndex++;
233
234             break;
235           }
236
237           r = resIndex;
238         }
239         else
240         {
241           break;
242         }
243       }
244
245       if (!found)
246       {
247         seqIndex++;
248         resIndex = -1;
249       }
250     }
251
252     /**
253      * We now search the Id string in the main search loop. for (int id = 0; id
254      * < alignment.getHeight(); id++) { if
255      * (regex.search(alignment.getSequenceAt(id).getName())) {
256      * idMatch.addElement(alignment.getSequenceAt(id)); hasResults = true; } }
257      */
258     return hasResults;
259   }
260
261   /**
262    * @return the alignment
263    */
264   public AlignmentI getAlignment()
265   {
266     return alignment;
267   }
268
269   /**
270    * @param alignment
271    *          the alignment to set
272    */
273   public void setAlignment(AlignmentI alignment)
274   {
275     this.alignment = alignment;
276   }
277
278   /**
279    * @return the caseSensitive
280    */
281   public boolean isCaseSensitive()
282   {
283     return caseSensitive;
284   }
285
286   /**
287    * @param caseSensitive
288    *          the caseSensitive to set
289    */
290   public void setCaseSensitive(boolean caseSensitive)
291   {
292     this.caseSensitive = caseSensitive;
293   }
294
295   /**
296    * @return the findAll
297    */
298   public boolean isFindAll()
299   {
300     return findAll;
301   }
302
303   /**
304    * @param findAll
305    *          the findAll to set
306    */
307   public void setFindAll(boolean findAll)
308   {
309     this.findAll = findAll;
310   }
311
312   /**
313    * @return the selection
314    */
315   public jalview.datamodel.SequenceGroup getSelection()
316   {
317     return selection;
318   }
319
320   /**
321    * @param selection
322    *          the selection to set
323    */
324   public void setSelection(jalview.datamodel.SequenceGroup selection)
325   {
326     this.selection = selection;
327   }
328
329   /**
330    * Returns the (possibly empty) list of matching sequences (when search
331    * includes searching sequence names)
332    * 
333    * @return
334    */
335   public Vector<SequenceI> getIdMatch()
336   {
337     return idMatch;
338   }
339
340   /**
341    * @return the regex
342    */
343   public com.stevesoft.pat.Regex getRegex()
344   {
345     return regex;
346   }
347
348   /**
349    * @return the searchResults
350    */
351   public SearchResultsI getSearchResults()
352   {
353     return searchResults;
354   }
355
356   /**
357    * @return the resIndex
358    */
359   public int getResIndex()
360   {
361     return resIndex;
362   }
363
364   /**
365    * @param resIndex
366    *          the resIndex to set
367    */
368   public void setResIndex(int resIndex)
369   {
370     this.resIndex = resIndex;
371   }
372
373   /**
374    * @return the seqIndex
375    */
376   public int getSeqIndex()
377   {
378     return seqIndex;
379   }
380
381   /**
382    * @param seqIndex
383    *          the seqIndex to set
384    */
385   public void setSeqIndex(int seqIndex)
386   {
387     this.seqIndex = seqIndex;
388   }
389
390   public boolean isIncludeDescription()
391   {
392     return includeDescription;
393   }
394
395   public void setIncludeDescription(boolean includeDescription)
396   {
397     this.includeDescription = includeDescription;
398   }
399 }