f6e586227c3c76ef20328b9cdc5a962340620e6b
[jalview.git] / src / jalview / datamodel / CigarArray.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.List;
24
25 public class CigarArray extends CigarBase
26 {
27   /**
28    * Do CIGAR operations on a set of sequences from many other cigars BAD THINGS
29    * WILL HAPPEN IF A CIGARARRAY IS PASSED TO A CIGARARRAY or a CIGARCIGAR is
30    * given a CIGARARRAY to insert gaps into.
31    */
32   /**
33    * array of subject cigars
34    */
35   public CigarSimple refCigars[] = null;
36
37   private boolean seqcigararray = false;
38
39   private CigarArray()
40   {
41     super();
42   }
43
44   /**
45    * isSeqCigarArray()
46    * 
47    * @return boolean true if all refCigars resolve to a SeqCigar or a CigarCigar
48    */
49   public boolean isSeqCigarArray()
50   {
51     return seqcigararray;
52   }
53
54   /**
55    * Apply CIGAR operations to several cigars in parallel will throw an error if
56    * any of cigar are actually CigarArrays.
57    * 
58    * @param cigar
59    *          Cigar[]
60    */
61   public CigarArray(CigarSimple[] cigars)
62   {
63     super();
64     seqcigararray = true;
65     if (cigars != null && cigars.length > 0)
66     {
67       refCigars = new CigarSimple[cigars.length];
68       for (int c = 0; c < cigars.length; c++)
69       {
70         refCigars[c] = cigars[c];
71         if (!((cigars[c] instanceof SeqCigar) || cigars[c] instanceof CigarCigar))
72         {
73           seqcigararray = false;
74         }
75       }
76     }
77   }
78
79   /**
80    * construct a cigar array from the current alignment, or just the subset of
81    * the current alignment specified by selectionGroup. Any columns marked as
82    * hidden in columnSelection will be marked as deleted in the array.
83    * 
84    * @param alignment
85    * @param columnSelection
86    * @param selectionGroup
87    */
88   public CigarArray(AlignmentI alignment, ColumnSelection columnSelection,
89           SequenceGroup selectionGroup)
90   {
91     this(constructSeqCigarArray(alignment, selectionGroup));
92     constructFromAlignment(alignment,
93             columnSelection != null ? columnSelection.getHiddenColumns()
94                     : null, selectionGroup);
95   }
96
97   private static int[] _calcStartEndBounds(AlignmentI alignment,
98           SequenceGroup selectionGroup)
99   {
100     int[] startend = new int[] { 0, 0, 0 };
101     if (selectionGroup != null)
102     {
103       startend[0] = selectionGroup.getSize();
104       startend[1] = selectionGroup.getStartRes();
105       startend[2] = selectionGroup.getEndRes(); // inclusive for start and end
106                                                 // in
107       // SeqCigar constructor
108     }
109     else
110     {
111       startend[0] = alignment.getHeight();
112       startend[2] = alignment.getWidth() - 1;
113     }
114     return startend;
115   }
116
117   public static SeqCigar[] constructSeqCigarArray(AlignmentI alignment,
118           SequenceGroup selectionGroup)
119   {
120     SequenceI[] seqs = null;
121     int i, iSize;
122     int _startend[] = _calcStartEndBounds(alignment, selectionGroup);
123     int start = _startend[1], end = _startend[2];
124     if (selectionGroup != null)
125     {
126       iSize = selectionGroup.getSize();
127       seqs = selectionGroup.getSequencesInOrder(alignment);
128       start = selectionGroup.getStartRes();
129       end = selectionGroup.getEndRes(); // inclusive for start and end in
130       // SeqCigar constructor
131     }
132     else
133     {
134       iSize = alignment.getHeight();
135       seqs = alignment.getSequencesArray();
136       end = alignment.getWidth() - 1;
137     }
138     SeqCigar[] selseqs = new SeqCigar[iSize];
139     for (i = 0; i < iSize; i++)
140     {
141       selseqs[i] = new SeqCigar(seqs[i], start, end);
142     }
143     return selseqs;
144   }
145
146   /**
147    * internal constructor function - called by CigarArray(AlignmentI, ...);
148    * 
149    * @param alignment
150    * @param list
151    *          - vector of visible regions as returned from
152    *          columnSelection.getHiddenColumns()
153    * @param selectionGroup
154    */
155   private void constructFromAlignment(AlignmentI alignment,
156           List<int[]> list, SequenceGroup selectionGroup)
157   {
158     int[] _startend = _calcStartEndBounds(alignment, selectionGroup);
159     int start = _startend[1], end = _startend[2];
160     // now construct the CigarArray operations
161     if (list != null)
162     {
163       int[] region;
164       int hideStart, hideEnd;
165       int last = start;
166       for (int j = 0; last < end & j < list.size(); j++)
167       {
168         region = list.get(j);
169         hideStart = region[0];
170         hideEnd = region[1];
171         // edit hidden regions to selection range
172         if (hideStart < last)
173         {
174           if (hideEnd > last)
175           {
176             hideStart = last;
177           }
178           else
179           {
180             continue;
181           }
182         }
183
184         if (hideStart > end)
185         {
186           break;
187         }
188
189         if (hideEnd > end)
190         {
191           hideEnd = end;
192         }
193
194         if (hideStart > hideEnd)
195         {
196           break;
197         }
198         /**
199          * form operations...
200          */
201         if (last < hideStart)
202         {
203           addOperation(CigarArray.M, hideStart - last);
204         }
205         addOperation(CigarArray.D, 1 + hideEnd - hideStart);
206         last = hideEnd + 1;
207       }
208       // Final match if necessary.
209       if (last < end)
210       {
211         addOperation(CigarArray.M, end - last + 1);
212       }
213     }
214     else
215     {
216       addOperation(CigarArray.M, end - start + 1);
217     }
218   }
219
220   /**
221    * @see Cigar.getSequenceAndDeletions
222    * @param GapChar
223    *          char
224    * @return Object[][]
225    */
226   protected Object[][] getArrayofSequenceAndDeletions(char GapChar)
227   {
228     if (refCigars == null || refCigars.length == 0 || length == 0)
229     {
230       return null;
231     }
232     Object[][] sqanddels = new Object[refCigars.length][];
233     for (int c = 0; c < refCigars.length; c++)
234     {
235       String refString = refCigars[c].getSequenceString(GapChar);
236       if (refString != null)
237       {
238         sqanddels[c] = getSequenceAndDeletions(refString, GapChar);
239       }
240       else
241       {
242         sqanddels[c] = null;
243       }
244     }
245     return sqanddels;
246   }
247
248   /**
249    * NOTE: this is an improper sequence string function
250    * 
251    * @return String formed by newline concatenated results of applying CIGAR
252    *         operations to each reference object in turn.
253    * @param GapChar
254    *          char
255    * @return '\n' separated strings (empty results included as \n\n)
256    */
257   public String getSequenceString(char GapChar)
258   {
259     if (length == 0 || refCigars == null)
260     {
261       return "";
262     }
263     StringBuffer seqStrings = new StringBuffer();
264     Object[][] sqanddels = getArrayofSequenceAndDeletions(GapChar);
265     for (int c = 0; c < refCigars.length; c++)
266     {
267       if (sqanddels[c] != null)
268       {
269         seqStrings.append((String) sqanddels[c][0]);
270         sqanddels[c][0] = null;
271       }
272       seqStrings.append('\n');
273     }
274     return seqStrings.toString();
275   }
276
277   /**
278    * return string results of applying cigar string to all reference cigars
279    * 
280    * @param GapChar
281    *          char
282    * @return String[]
283    */
284   public String[] getSequenceStrings(char GapChar)
285   {
286
287     if (length == 0 || refCigars == null || refCigars.length == 0)
288     {
289       return null;
290     }
291     Object[][] sqanddels = getArrayofSequenceAndDeletions(GapChar);
292     String[] seqs = new String[sqanddels.length];
293     for (int c = 0; c < refCigars.length; c++)
294     {
295       seqs[c] = (String) sqanddels[c][0];
296     }
297     return seqs;
298   }
299
300   /**
301    * Combines the CigarArray cigar operations with the operations in each
302    * reference cigar - creating a new reference cigar
303    * 
304    * @return Cigar[]
305    * 
306    *         public CigarBase[] getEditedCigars() {
307    * 
308    *         return new CigarBase[] {}; }
309    */
310   /**
311    * applyDeletions edits underlying refCigars to propagate deleted regions, and
312    * removes deletion operations from CigarArray operation list.
313    * 
314    * @return int[] position after deletion occured and range of deletion in
315    *         cigarArray or null if none occured
316    */
317   public int[] applyDeletions()
318   {
319     java.util.Vector delpos = null;
320     if (length == 0)
321     {
322       return null;
323     }
324     int cursor = 0; // range counter for deletions
325     int vcursor = 0; // visible column index
326     int offset = 0; // shift in visible column index as deletions are made
327     int i = 0;
328     while (i < length)
329     {
330       if (operation[i] != D)
331       {
332         if (operation[i] == M)
333         {
334           cursor += range[i];
335         }
336         vcursor += range[i++];
337       }
338       else
339       {
340         if (delpos == null)
341         {
342           delpos = new java.util.Vector();
343         }
344         int delstart = cursor, delend = cursor + range[i] - 1; // inclusive
345         delpos.addElement(new int[] { vcursor + offset, range[i] }); // index of
346                                                                      // right
347                                                                      // hand
348                                                                      // column
349                                                                      // after
350         // hidden region boundary
351         offset += range[i] - 1; // shift in visible column coordinates
352         System.arraycopy(operation, i + 1, operation, i, length - i);
353         System.arraycopy(range, i + 1, range, i, length - i);
354         length--;
355         /*
356          * int dmax=0; for (int s=0; s<refCigars.length; s++) { int d =
357          * refCigars[s].deleteRange(delstart, delend); if (d>dmax) dmax=d; }
358          * offset+=dmax; // shift in visible column coordinates
359          */
360         for (int s = 0; s < refCigars.length; s++)
361         {
362           int d = refCigars[s].deleteRange(delstart, delend);
363         }
364
365       }
366     }
367     if (delpos != null)
368     {
369       int[] pos = new int[delpos.size() * 2];
370       for (int k = 0, l = delpos.size(); k < l; k++)
371       {
372         int[] dr = ((int[]) delpos.elementAt(k));
373         pos[k * 2] = dr[0];
374         pos[k * 2 + 1] = dr[1];
375         delpos.setElementAt(null, k);
376       }
377       delpos = null;
378       return pos;
379     }
380     return null;
381   }
382
383   /**
384    * 
385    * @return SeqCigar[] or null if CigarArray is not a SeqCigarArray (ie it does
386    *         not resolve to set of seqCigars)
387    */
388   public SeqCigar[] getSeqCigarArray()
389   {
390     if (!isSeqCigarArray())
391     {
392       return null;
393     }
394     SeqCigar[] sa = new SeqCigar[refCigars.length];
395     for (int i = 0; i < refCigars.length; i++)
396     {
397       sa[i] = (SeqCigar) refCigars[i];
398     }
399     return sa;
400   }
401 }