5f605309ac1297d66b9eec58dab09129d506a905
[jalview.git] / src / jalview / datamodel / Alignment.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer
3  * Copyright (C) 2006 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18  */
19 package jalview.datamodel;
20
21 import jalview.analysis.*;
22
23 import jalview.util.*;
24
25 import java.util.*;
26
27 /** Data structure to hold and manipulate a multiple sequence alignment
28  */
29 public class Alignment implements AlignmentI
30 {
31     protected Alignment dataset;
32     protected Vector sequences;
33     protected Vector groups = new Vector();
34     protected char gapCharacter = '-';
35     protected int type = NUCLEOTIDE;
36     public static final int PROTEIN = 0;
37     public static final int NUCLEOTIDE = 1;
38
39     /** DOCUMENT ME!! */
40     public AlignmentAnnotation[] annotations;
41
42     HiddenSequences hiddenSequences = new HiddenSequences(this);
43
44     private void initAlignment(SequenceI[] seqs) {
45       int i=0;
46
47       if( jalview.util.Comparison.isNucleotide(seqs))
48         type = NUCLEOTIDE;
49       else
50         type = PROTEIN;
51
52       sequences = new Vector();
53
54       for (i = 0; i < seqs.length; i++)
55       {
56         sequences.addElement(seqs[i]);
57       }
58
59     }
60     /** Make an alignment from an array of Sequences.
61      *
62      * @param sequences
63      */
64     public Alignment(SequenceI[] seqs)
65     {
66       initAlignment(seqs);
67     }
68     /**
69      * Make a new alignment from an array of SeqCigars
70      * @param seqs SeqCigar[]
71      */
72     public Alignment(SeqCigar[] alseqs) {
73       SequenceI[] seqs = SeqCigar.createAlignmentSequences(alseqs, gapCharacter, new ColumnSelection(), null);
74       initAlignment(seqs);
75     }
76     /**
77      * Make a new alignment from an CigarArray
78      * JBPNote - can only do this when compactAlignment does not contain hidden regions.
79      * JBPNote - must also check that compactAlignment resolves to a set of SeqCigars - or construct them appropriately.
80      * @param compactAlignment CigarArray
81      */
82     public static AlignmentI createAlignment(CigarArray compactAlignment) {
83       throw new Error("Alignment(CigarArray) not yet implemented");
84       // this(compactAlignment.refCigars);
85     }
86
87     /**
88      * DOCUMENT ME!
89      *
90      * @return DOCUMENT ME!
91      */
92     public Vector getSequences()
93     {
94         return sequences;
95     }
96
97     public SequenceI [] getSequencesArray()
98     {
99       SequenceI [] reply = new SequenceI[sequences.size()];
100       for(int i=0; i<sequences.size(); i++)
101       {
102         reply[i] = (SequenceI)sequences.elementAt(i);
103       }
104       return reply;
105     }
106
107     /**
108      * DOCUMENT ME!
109      *
110      * @param i DOCUMENT ME!
111      *
112      * @return DOCUMENT ME!
113      */
114     public SequenceI getSequenceAt(int i)
115     {
116         if (i < sequences.size())
117         {
118             return (SequenceI) sequences.elementAt(i);
119         }
120
121         return null;
122     }
123
124     /** Adds a sequence to the alignment.  Recalculates maxLength and size.
125      *
126      * @param snew
127      */
128     public void addSequence(SequenceI snew)
129     {
130       if(dataset!=null)
131       {
132         if(snew.getDatasetSequence()!=null)
133         {
134           System.out.println(snew.getName());
135           getDataset().addSequence(snew.getDatasetSequence());
136         }
137         else
138         {
139           Sequence ds = new Sequence(snew.getName(),
140                                      AlignSeq.extractGaps("-. ",
141               snew.getSequence()),
142                                      snew.getStart(),
143                                      snew.getEnd());
144
145           snew.setDatasetSequence(ds);
146           getDataset().addSequence(ds);
147         }
148       }
149       sequences.addElement(snew);
150
151       hiddenSequences.adjustHeightSequenceAdded();
152     }
153
154
155     /** Adds a sequence to the alignment.  Recalculates maxLength and size.
156      *
157      * @param snew
158      */
159     public void setSequenceAt(int i, SequenceI snew)
160     {
161         SequenceI oldseq = getSequenceAt(i);
162         deleteSequence(oldseq);
163
164         sequences.setElementAt(snew, i);
165     }
166
167     /**
168      * DOCUMENT ME!
169      *
170      * @return DOCUMENT ME!
171      */
172     public Vector getGroups()
173     {
174         return groups;
175     }
176     /** Takes out columns consisting entirely of gaps (-,.," ")
177      */
178     public void removeGaps() {
179       removeGaps((ShiftList)null);
180     }
181     /**
182      * remove gaps in alignment - recording any frame shifts in shiftrecord
183      * intended to be passed to ColumnSelection.compensateForEdits(shiftrecord)
184      * @param shiftrecord
185      */
186     public void removeGaps(ShiftList shiftrecord) {
187         SequenceI[] seqs = getVisibleAndRepresentedSeqs();
188         int j, jSize = seqs.length;
189
190         int width = 0;
191         for (int i = 0; i < jSize; i++)
192         {
193           if (seqs[i].getLength() > width)
194           {
195             width = seqs[i].getLength();
196           }
197         }
198
199         int startCol = -1, endCol = -1;
200         boolean delete = true;
201         for (int i = 0; i < width; i++)
202         {
203             delete = true;
204
205             for (j = 0; j < jSize; j++)
206             {
207                 if (seqs[j].getLength() > i)
208                 {
209                     if (!jalview.util.Comparison.isGap(seqs[j].getCharAt(i)))
210                     {
211                         if(delete)
212                           endCol = i;
213
214                         delete = false;
215                         break;
216                     }
217                 }
218             }
219
220             if(delete && startCol==-1)
221             {
222               startCol = i;
223             }
224
225
226             if (!delete && startCol > -1)
227             {
228               deleteColumns(seqs, startCol, endCol);
229               if (shiftrecord!=null) {
230                 shiftrecord.addShift(startCol, 1+endCol-startCol);
231               }
232               width -= (endCol - startCol);
233               i -= (endCol - startCol);
234               startCol = -1;
235               endCol = -1;
236             }
237         }
238
239         if (delete && startCol > -1)
240         {
241           deleteColumns(seqs, startCol, endCol);
242           if (shiftrecord!=null) {
243             shiftrecord.addShift(startCol, 1+endCol-startCol);
244           }
245         }
246     }
247
248     /** Removes a range of columns (start to end inclusive).
249      *
250      * @param seqs Sequences to remove columns from
251      * @param start Start column in the alignment
252      * @param end End column in the alignment
253      */
254     public void deleteColumns(SequenceI [] seqs, int start, int end)
255     {
256       for(int i=0; i<seqs.length; i++)
257         seqs[i].deleteChars(start, end);
258     }
259
260
261     /**
262      * DOCUMENT ME!
263      *
264      * @param i DOCUMENT ME!
265      */
266     public void trimLeft(int i)
267     {
268         SequenceI[] seqs = getVisibleAndRepresentedSeqs();
269         int j, jSize = seqs.length;
270         for (j = 0; j < jSize; j++)
271         {
272             int newstart = seqs[j].findPosition(i);
273
274             if(i>seqs[j].getLength())
275             {
276               sequences.removeElement(seqs[j]);
277               j--;
278               jSize--;
279             }
280             else
281             {
282               seqs[j].setStart(newstart);
283               seqs[j].setSequence(seqs[j].getSequence().substring(i));
284             }
285         }
286     }
287
288     /**
289      * DOCUMENT ME!
290      *
291      * @param i DOCUMENT ME!
292      */
293     public void trimRight(int i)
294     {
295         SequenceI[] seqs = getVisibleAndRepresentedSeqs();
296         int j, jSize = seqs.length;
297         for (j = 0; j < jSize; j++)
298         {
299             int newend = seqs[j].findPosition(i);
300
301             seqs[j].setEnd(newend);
302             if(seqs[j].getLength()>i)
303               seqs[j].setSequence(seqs[j].getSequence().substring(0, i + 1));
304         }
305     }
306
307     /**
308      * DOCUMENT ME!
309      *
310      * @param s DOCUMENT ME!
311      */
312     public void deleteSequence(SequenceI s)
313     {
314       deleteSequence(findIndex(s));
315     }
316
317     /**
318      * DOCUMENT ME!
319      *
320      * @param i DOCUMENT ME!
321      */
322     public void deleteSequence(int i)
323     {
324         sequences.removeElementAt(i);
325         hiddenSequences.adjustHeightSequenceDeleted(i);
326     }
327
328
329     /**    */
330     public SequenceGroup findGroup(SequenceI s)
331     {
332         for (int i = 0; i < this.groups.size(); i++)
333         {
334             SequenceGroup sg = (SequenceGroup) groups.elementAt(i);
335
336             if (sg.getSequences(false).contains(s))
337             {
338                 return sg;
339             }
340         }
341
342         return null;
343     }
344
345     /**
346      * DOCUMENT ME!
347      *
348      * @param s DOCUMENT ME!
349      *
350      * @return DOCUMENT ME!
351      */
352     public SequenceGroup[] findAllGroups(SequenceI s)
353     {
354         Vector temp = new Vector();
355
356         int gSize = groups.size();
357         for (int i = 0; i < gSize; i++)
358         {
359             SequenceGroup sg = (SequenceGroup) groups.elementAt(i);
360             if(sg==null || sg.getSequences(false)==null)
361             {
362               this.deleteGroup(sg);
363               gSize--;
364               continue;
365             }
366
367             if (sg.getSequences(false).contains(s))
368             {
369                 temp.addElement(sg);
370             }
371         }
372
373         SequenceGroup[] ret = new SequenceGroup[temp.size()];
374
375         for (int i = 0; i < temp.size(); i++)
376         {
377             ret[i] = (SequenceGroup) temp.elementAt(i);
378         }
379
380         return ret;
381     }
382
383
384
385     /**    */
386     public void addGroup(SequenceGroup sg)
387     {
388         if (!groups.contains(sg))
389         {
390             groups.addElement(sg);
391         }
392     }
393
394     /**
395      * DOCUMENT ME!
396      */
397     public void deleteAllGroups()
398     {
399         groups.removeAllElements();
400
401         int i = 0;
402
403         while (i < sequences.size())
404         {
405             SequenceI s = getSequenceAt(i);
406             s.setColor(java.awt.Color.white);
407             i++;
408         }
409     }
410
411     /**    */
412     public void deleteGroup(SequenceGroup g)
413     {
414         if (groups.contains(g))
415         {
416             groups.removeElement(g);
417         }
418     }
419
420     /**    */
421     public SequenceI findName(String name)
422     {
423         int i = 0;
424
425         while (i < sequences.size())
426         {
427             if (getSequenceAt(i).getName().equals(name))
428             {
429                 return getSequenceAt(i);
430             }
431
432             i++;
433         }
434
435         return null;
436     }
437
438     public SequenceI [] findSequenceMatch(String name)
439     {
440       Vector matches = new Vector();
441       int i = 0;
442
443       while (i < sequences.size())
444       {
445           if (getSequenceAt(i).getName().equals(name))
446           {
447               matches.addElement(getSequenceAt(i));
448           }
449           i++;
450       }
451
452       SequenceI [] result = new SequenceI[matches.size()];
453       for(i=0; i<result.length; i++)
454         result[i] = (SequenceI)matches.elementAt(i);
455
456       return result;
457
458     }
459
460
461     /**    */
462     public int findIndex(SequenceI s)
463     {
464         int i = 0;
465
466         while (i < sequences.size())
467         {
468             if (s == getSequenceAt(i))
469             {
470                 return i;
471             }
472
473             i++;
474         }
475
476         return -1;
477     }
478
479     /**
480      * DOCUMENT ME!
481      *
482      * @return DOCUMENT ME!
483      */
484     public int getHeight()
485     {
486         return sequences.size();
487     }
488
489     /**
490      * DOCUMENT ME!
491      *
492      * @return DOCUMENT ME!
493      */
494     public int getWidth()
495     {
496         int maxLength = -1;
497
498         for (int i = 0; i < sequences.size(); i++)
499         {
500             if (getSequenceAt(i).getLength() > maxLength)
501             {
502                 maxLength = getSequenceAt(i).getLength();
503             }
504         }
505
506         return maxLength;
507     }
508
509     /**
510      * DOCUMENT ME!
511      *
512      * @return DOCUMENT ME!
513      */
514     public int getMaxIdLength()
515     {
516         int max = 0;
517         int i = 0;
518
519         while (i < sequences.size())
520         {
521             SequenceI seq = getSequenceAt(i);
522             String tmp = seq.getName() + "/" + seq.getStart() + "-" +
523                 seq.getEnd();
524
525             if (tmp.length() > max)
526             {
527                 max = tmp.length();
528             }
529
530             i++;
531         }
532
533         return max;
534     }
535
536     /**
537      * DOCUMENT ME!
538      *
539      * @param gc DOCUMENT ME!
540      */
541     public void setGapCharacter(char gc)
542     {
543         gapCharacter = gc;
544
545         for (int i = 0; i < sequences.size(); i++)
546         {
547             Sequence seq = (Sequence) sequences.elementAt(i);
548             seq.setSequence( seq.getSequence().replace('.', gc) );
549             seq.setSequence( seq.getSequence().replace('-', gc) );
550             seq.setSequence( seq.getSequence().replace(' ', gc) );
551         }
552     }
553
554     /**
555      * DOCUMENT ME!
556      *
557      * @return DOCUMENT ME!
558      */
559     public char getGapCharacter()
560     {
561         return gapCharacter;
562     }
563
564
565     /**
566      * DOCUMENT ME!
567      *
568      * @return DOCUMENT ME!
569      */
570     public boolean isAligned()
571     {
572         int width = getWidth();
573
574         for (int i = 0; i < sequences.size(); i++)
575         {
576             if (getSequenceAt(i).getLength() != width)
577             {
578                 return false;
579             }
580         }
581
582         return true;
583     }
584
585     /**
586      * DOCUMENT ME!
587      *
588      * @param aa DOCUMENT ME!
589      */
590     public void deleteAnnotation(AlignmentAnnotation aa)
591     {
592         int aSize = 1;
593
594         if (annotations != null)
595         {
596             aSize = annotations.length;
597         }
598
599         AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize - 1];
600
601         int tIndex = 0;
602
603         for (int i = 0; i < aSize; i++)
604         {
605             if (annotations[i] == aa)
606             {
607                 continue;
608             }
609
610             temp[tIndex] = annotations[i];
611             tIndex++;
612         }
613
614         annotations = temp;
615     }
616
617
618     public void adjustSequenceAnnotations()
619     {
620       if(annotations!=null)
621       {
622         for (int a = 0; a < annotations.length; a++)
623         {
624           if (annotations[a].sequenceRef != null)
625           {
626             annotations[a].adjustForAlignment();
627           }
628         }
629       }
630     }
631
632     /**
633      * DOCUMENT ME!
634      *
635      * @param aa DOCUMENT ME!
636      */
637     public void addAnnotation(AlignmentAnnotation aa)
638     {
639         int aSize = 1;
640         if (annotations != null)
641         {
642             aSize = annotations.length + 1;
643         }
644
645         AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize];
646
647         temp[aSize-1] = aa;
648
649         int i = 0;
650
651         if (aSize > 1)
652         {
653             for (i = 0; i < (aSize-1); i++)
654             {
655                 temp[i] = annotations[i];
656             }
657         }
658
659         annotations = temp;
660     }
661
662     public void setAnnotationIndex(AlignmentAnnotation aa, int index)
663     {
664       if(aa==null || annotations==null || annotations.length-1<index)
665         return;
666
667       int aSize = annotations.length;
668       AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize];
669
670       temp[index] = aa;
671
672       for (int i = 0; i < aSize; i++)
673       {
674         if(i==index)
675           continue;
676
677         if(i<index)
678           temp[i] = annotations[i];
679         else
680           temp[i] = annotations[i-1];
681       }
682
683         annotations = temp;
684     }
685
686     /**
687      * DOCUMENT ME!
688      *
689      * @return DOCUMENT ME!
690      */
691     public AlignmentAnnotation[] getAlignmentAnnotation()
692     {
693         return annotations;
694     }
695
696     public void setNucleotide(boolean b)
697     {
698       if(b)
699         type = NUCLEOTIDE;
700       else
701         type = PROTEIN;
702     }
703
704     public boolean isNucleotide()
705     {
706       if(type==NUCLEOTIDE)
707         return true;
708       else
709         return false;
710     }
711
712     public void setDataset(Alignment data)
713     {
714       if(dataset==null && data==null)
715       {
716         // Create a new dataset for this alignment.
717         // Can only be done once, if dataset is not null
718         // This will not be performed
719         Sequence[] seqs = new Sequence[getHeight()];
720         for (int i = 0; i < getHeight(); i++)
721         {
722           if(getSequenceAt(i).getDatasetSequence()!=null)
723           {
724             seqs[i] = (Sequence)getSequenceAt(i).getDatasetSequence();
725           }
726           else
727           {
728             seqs[i] = new Sequence(getSequenceAt(i).getName(),
729                                    AlignSeq.extractGaps(
730                                        jalview.util.Comparison.GapChars,
731                                        getSequenceAt(i).getSequence()
732                                    ),
733                                    getSequenceAt(i).getStart(),
734                                    getSequenceAt(i).getEnd());
735             seqs[i].sequenceFeatures = getSequenceAt(i).getSequenceFeatures();
736             getSequenceAt(i).setSequenceFeatures(null);
737             getSequenceAt(i).setDatasetSequence(seqs[i]);
738           }
739         }
740
741         dataset = new Alignment(seqs);
742       }
743       else if(dataset==null && data!=null)
744       {
745         dataset = data;
746       }
747     }
748
749     public Alignment getDataset()
750     {
751       return dataset;
752     }
753
754     public boolean padGaps() {
755       boolean modified=false;
756
757       //Remove excess gaps from the end of alignment
758       int maxLength = -1;
759
760       SequenceI current;
761       for (int i = 0; i < sequences.size(); i++)
762       {
763         current = getSequenceAt(i);
764         for (int j = current.getLength(); j > maxLength; j--)
765         {
766           if (j > maxLength && !jalview.util.Comparison.isGap(
767               current.getCharAt(j)))
768           {
769             maxLength = j;
770             break;
771           }
772         }
773       }
774
775       maxLength++;
776
777       for (int i = 0; i < sequences.size();
778            i++)
779       {
780         current = getSequenceAt(i);
781
782         if (current.getLength() < maxLength)
783         {
784           current.insertCharAt(maxLength - 1, gapCharacter);
785           modified=true;
786         }
787         else if(current.getLength() > maxLength)
788         {
789           current.deleteChars(maxLength, current.getLength());
790         }
791       }
792       return modified;
793     }
794
795     public HiddenSequences getHiddenSequences()
796     {
797       return hiddenSequences;
798     }
799     SequenceI [] getVisibleAndRepresentedSeqs()
800     {
801       if(hiddenSequences==null || hiddenSequences.getSize()<1)
802         return getSequencesArray();
803
804       Vector seqs = new Vector();
805       SequenceI seq;
806       SequenceGroup hidden;
807       for (int i = 0; i < sequences.size(); i++)
808       {
809         seq = (SequenceI) sequences.elementAt(i);
810         seqs.addElement(seq);
811         hidden = seq.getHiddenSequences();
812         if(hidden!=null)
813         {
814           for(int j=0; j<hidden.getSize(false); j++)
815           {
816             seqs.addElement(hidden.getSequenceAt(j));
817           }
818         }
819       }
820       SequenceI [] result = new SequenceI[seqs.size()];
821       for(int i=0; i<seqs.size(); i++)
822         result[i] = (SequenceI)seqs.elementAt(i);
823
824       return result;
825
826     }
827
828   public CigarArray getCompactAlignment()
829   {
830     SeqCigar alseqs[] = new SeqCigar[sequences.size()];
831     for (int i=0; i<sequences.size(); i++) {
832       alseqs[i] = new SeqCigar((SequenceI) sequences.elementAt(i));
833     }
834     CigarArray cal = new CigarArray(alseqs);
835     cal.addOperation(CigarArray.M, getWidth());
836     return cal;
837   }
838
839 }