merge from 2_4_Release branch
[jalview.git] / src / jalview / datamodel / Alignment.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.4)
3  * Copyright (C) 2008 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 java.util.*;
22
23 import jalview.analysis.*;
24
25 /**
26  * Data structure to hold and manipulate a multiple sequence alignment
27  */
28 public class Alignment implements AlignmentI
29 {
30   protected Alignment dataset;
31
32   protected Vector sequences;
33
34   protected Vector groups = new Vector();
35
36   protected char gapCharacter = '-';
37
38   protected int type = NUCLEOTIDE;
39
40   public static final int PROTEIN = 0;
41
42   public static final int NUCLEOTIDE = 1;
43
44   /** DOCUMENT ME!! */
45   public AlignmentAnnotation[] annotations;
46
47   HiddenSequences hiddenSequences = new HiddenSequences(this);
48
49   public Hashtable alignmentProperties;
50
51   private void initAlignment(SequenceI[] seqs)
52   {
53     int i = 0;
54
55     if (jalview.util.Comparison.isNucleotide(seqs))
56     {
57       type = NUCLEOTIDE;
58     }
59     else
60     {
61       type = PROTEIN;
62     }
63
64     sequences = new Vector();
65
66     for (i = 0; i < seqs.length; i++)
67     {
68       sequences.addElement(seqs[i]);
69     }
70
71   }
72
73   /**
74    * Make an alignment from an array of Sequences.
75    * 
76    * @param sequences
77    */
78   public Alignment(SequenceI[] seqs)
79   {
80     initAlignment(seqs);
81   }
82
83   /**
84    * Make a new alignment from an array of SeqCigars
85    * 
86    * @param seqs
87    *                SeqCigar[]
88    */
89   public Alignment(SeqCigar[] alseqs)
90   {
91     SequenceI[] seqs = SeqCigar.createAlignmentSequences(alseqs,
92             gapCharacter, new ColumnSelection(), null);
93     initAlignment(seqs);
94   }
95
96   /**
97    * Make a new alignment from an CigarArray JBPNote - can only do this when
98    * compactAlignment does not contain hidden regions. JBPNote - must also check
99    * that compactAlignment resolves to a set of SeqCigars - or construct them
100    * appropriately.
101    * 
102    * @param compactAlignment
103    *                CigarArray
104    */
105   public static AlignmentI createAlignment(CigarArray compactAlignment)
106   {
107     throw new Error("Alignment(CigarArray) not yet implemented");
108     // this(compactAlignment.refCigars);
109   }
110
111   /**
112    * DOCUMENT ME!
113    * 
114    * @return DOCUMENT ME!
115    */
116   public Vector getSequences()
117   {
118     return sequences;
119   }
120
121   public SequenceI[] getSequencesArray()
122   {
123     if (sequences == null)
124       return null;
125     SequenceI[] reply = new SequenceI[sequences.size()];
126     for (int i = 0; i < sequences.size(); i++)
127     {
128       reply[i] = (SequenceI) sequences.elementAt(i);
129     }
130     return reply;
131   }
132
133   /**
134    * DOCUMENT ME!
135    * 
136    * @param i
137    *                DOCUMENT ME!
138    * 
139    * @return DOCUMENT ME!
140    */
141   public SequenceI getSequenceAt(int i)
142   {
143     if (i < sequences.size())
144     {
145       return (SequenceI) sequences.elementAt(i);
146     }
147
148     return null;
149   }
150
151   /**
152    * Adds a sequence to the alignment. Recalculates maxLength and size.
153    * 
154    * @param snew
155    */
156   public void addSequence(SequenceI snew)
157   {
158     if (dataset != null)
159     {
160       // maintain dataset integrity
161       if (snew.getDatasetSequence() != null)
162       {
163         getDataset().addSequence(snew.getDatasetSequence());
164       }
165       else
166       {
167         // derive new sequence
168         SequenceI adding = snew.deriveSequence();
169         getDataset().addSequence(adding.getDatasetSequence());
170         snew = adding;
171       }
172     }
173     if (sequences == null)
174     {
175       initAlignment(new SequenceI[]
176       { snew });
177     }
178     else
179     {
180       sequences.addElement(snew);
181     }
182     if (hiddenSequences != null)
183       hiddenSequences.adjustHeightSequenceAdded();
184   }
185
186   /**
187    * Adds a sequence to the alignment. Recalculates maxLength and size.
188    * 
189    * @param snew
190    */
191   public void setSequenceAt(int i, SequenceI snew)
192   {
193     SequenceI oldseq = getSequenceAt(i);
194     deleteSequence(oldseq);
195
196     sequences.setElementAt(snew, i);
197   }
198
199   /**
200    * DOCUMENT ME!
201    * 
202    * @return DOCUMENT ME!
203    */
204   public Vector getGroups()
205   {
206     return groups;
207   }
208
209   public void finalize()
210   {
211     if (getDataset() != null)
212       getDataset().removeAlignmentRef();
213
214     dataset = null;
215     sequences = null;
216     groups = null;
217     annotations = null;
218     hiddenSequences = null;
219   }
220
221   /**
222    * decrement the alignmentRefs counter by one and call finalize if it goes to
223    * zero.
224    */
225   private void removeAlignmentRef()
226   {
227     if (--alignmentRefs == 0)
228     {
229       finalize();
230     }
231   }
232
233   /**
234    * DOCUMENT ME!
235    * 
236    * @param s
237    *                DOCUMENT ME!
238    */
239   public void deleteSequence(SequenceI s)
240   {
241     deleteSequence(findIndex(s));
242   }
243
244   /**
245    * DOCUMENT ME!
246    * 
247    * @param i
248    *                DOCUMENT ME!
249    */
250   public void deleteSequence(int i)
251   {
252     if (i > -1 && i < getHeight())
253     {
254       sequences.removeElementAt(i);
255       hiddenSequences.adjustHeightSequenceDeleted(i);
256     }
257   }
258
259   /**    */
260   public SequenceGroup findGroup(SequenceI s)
261   {
262     for (int i = 0; i < this.groups.size(); i++)
263     {
264       SequenceGroup sg = (SequenceGroup) groups.elementAt(i);
265
266       if (sg.getSequences(null).contains(s))
267       {
268         return sg;
269       }
270     }
271
272     return null;
273   }
274
275   /**
276    * DOCUMENT ME!
277    * 
278    * @param s
279    *                DOCUMENT ME!
280    * 
281    * @return DOCUMENT ME!
282    */
283   public SequenceGroup[] findAllGroups(SequenceI s)
284   {
285     Vector temp = new Vector();
286
287     int gSize = groups.size();
288     for (int i = 0; i < gSize; i++)
289     {
290       SequenceGroup sg = (SequenceGroup) groups.elementAt(i);
291       if (sg == null || sg.getSequences(null) == null)
292       {
293         this.deleteGroup(sg);
294         gSize--;
295         continue;
296       }
297
298       if (sg.getSequences(null).contains(s))
299       {
300         temp.addElement(sg);
301       }
302     }
303
304     SequenceGroup[] ret = new SequenceGroup[temp.size()];
305
306     for (int i = 0; i < temp.size(); i++)
307     {
308       ret[i] = (SequenceGroup) temp.elementAt(i);
309     }
310
311     return ret;
312   }
313
314   /**    */
315   public void addGroup(SequenceGroup sg)
316   {
317     if (!groups.contains(sg))
318     {
319       if (hiddenSequences.getSize() > 0)
320       {
321         int i, iSize = sg.getSize();
322         for (i = 0; i < iSize; i++)
323         {
324           if (!sequences.contains(sg.getSequenceAt(i)))
325           {
326             sg.deleteSequence(sg.getSequenceAt(i), false);
327             iSize--;
328             i--;
329           }
330         }
331
332         if (sg.getSize() < 1)
333         {
334           return;
335         }
336       }
337
338       groups.addElement(sg);
339     }
340   }
341
342   /**
343    * DOCUMENT ME!
344    */
345   public void deleteAllGroups()
346   {
347     groups.removeAllElements();
348   }
349
350   /**    */
351   public void deleteGroup(SequenceGroup g)
352   {
353     if (groups.contains(g))
354     {
355       groups.removeElement(g);
356     }
357   }
358
359   /**    */
360   public SequenceI findName(String name)
361   {
362     return findName(name, false);
363   }
364
365   /*
366    * (non-Javadoc)
367    * 
368    * @see jalview.datamodel.AlignmentI#findName(java.lang.String, boolean)
369    */
370   public SequenceI findName(String token, boolean b)
371   {
372     return findName(null, token, b);
373   }
374
375   /*
376    * (non-Javadoc)
377    * 
378    * @see jalview.datamodel.AlignmentI#findName(SequenceI, java.lang.String,
379    *      boolean)
380    */
381   public SequenceI findName(SequenceI startAfter, String token, boolean b)
382   {
383
384     int i = 0;
385     SequenceI sq = null;
386     String sqname = null;
387     if (startAfter != null)
388     {
389       // try to find the sequence in the alignment
390       boolean matched = false;
391       while (i < sequences.size())
392       {
393         if (getSequenceAt(i++) == startAfter)
394         {
395           matched = true;
396           break;
397         }
398       }
399       if (!matched)
400       {
401         i = 0;
402       }
403     }
404     while (i < sequences.size())
405     {
406       sq = getSequenceAt(i);
407       sqname = sq.getName();
408       if (sqname.equals(token) // exact match
409               || (b && // allow imperfect matches - case varies
410               (sqname.equalsIgnoreCase(token))))
411       {
412         return getSequenceAt(i);
413       }
414
415       i++;
416     }
417
418     return null;
419   }
420
421   public SequenceI[] findSequenceMatch(String name)
422   {
423     Vector matches = new Vector();
424     int i = 0;
425
426     while (i < sequences.size())
427     {
428       if (getSequenceAt(i).getName().equals(name))
429       {
430         matches.addElement(getSequenceAt(i));
431       }
432       i++;
433     }
434
435     SequenceI[] result = new SequenceI[matches.size()];
436     for (i = 0; i < result.length; i++)
437     {
438       result[i] = (SequenceI) matches.elementAt(i);
439     }
440
441     return result;
442
443   }
444
445   /**    */
446   public int findIndex(SequenceI s)
447   {
448     int i = 0;
449
450     while (i < sequences.size())
451     {
452       if (s == getSequenceAt(i))
453       {
454         return i;
455       }
456
457       i++;
458     }
459
460     return -1;
461   }
462
463   /**
464    * DOCUMENT ME!
465    * 
466    * @return DOCUMENT ME!
467    */
468   public int getHeight()
469   {
470     return sequences.size();
471   }
472
473   /**
474    * DOCUMENT ME!
475    * 
476    * @return DOCUMENT ME!
477    */
478   public int getWidth()
479   {
480     int maxLength = -1;
481
482     for (int i = 0; i < sequences.size(); i++)
483     {
484       if (getSequenceAt(i).getLength() > maxLength)
485       {
486         maxLength = getSequenceAt(i).getLength();
487       }
488     }
489
490     return maxLength;
491   }
492
493   /**
494    * DOCUMENT ME!
495    * 
496    * @param gc
497    *                DOCUMENT ME!
498    */
499   public void setGapCharacter(char gc)
500   {
501     gapCharacter = gc;
502
503     for (int i = 0; i < sequences.size(); i++)
504     {
505       Sequence seq = (Sequence) sequences.elementAt(i);
506       seq.setSequence(seq.getSequenceAsString().replace('.', gc).replace(
507               '-', gc).replace(' ', gc));
508     }
509   }
510
511   /**
512    * DOCUMENT ME!
513    * 
514    * @return DOCUMENT ME!
515    */
516   public char getGapCharacter()
517   {
518     return gapCharacter;
519   }
520
521   /**
522    * DOCUMENT ME!
523    * 
524    * @return DOCUMENT ME!
525    */
526   public boolean isAligned()
527   {
528     int width = getWidth();
529
530     for (int i = 0; i < sequences.size(); i++)
531     {
532       if (getSequenceAt(i).getLength() != width)
533       {
534         return false;
535       }
536     }
537
538     return true;
539   }
540
541   /*
542    * (non-Javadoc)
543    * 
544    * @see jalview.datamodel.AlignmentI#deleteAnnotation(jalview.datamodel.AlignmentAnnotation)
545    */
546   public boolean deleteAnnotation(AlignmentAnnotation aa)
547   {
548     int aSize = 1;
549
550     if (annotations != null)
551     {
552       aSize = annotations.length;
553     }
554
555     if (aSize < 1)
556     {
557       return false;
558     }
559
560     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize - 1];
561
562     boolean swap = false;
563     int tIndex = 0;
564
565     for (int i = 0; i < aSize; i++)
566     {
567       if (annotations[i] == aa)
568       {
569         swap = true;
570         continue;
571       }
572       if (tIndex < temp.length)
573         temp[tIndex++] = annotations[i];
574     }
575
576     if (swap)
577     {
578       annotations = temp;
579       if (aa.sequenceRef != null)
580         aa.sequenceRef.removeAlignmentAnnotation(aa);
581     }
582     return swap;
583   }
584
585   /**
586    * DOCUMENT ME!
587    * 
588    * @param aa
589    *                DOCUMENT ME!
590    */
591   public void addAnnotation(AlignmentAnnotation aa)
592   {
593     int aSize = 1;
594     if (annotations != null)
595     {
596       aSize = annotations.length + 1;
597     }
598
599     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize];
600
601     temp[aSize - 1] = aa;
602
603     int i = 0;
604
605     if (aSize > 1)
606     {
607       for (i = 0; i < (aSize - 1); i++)
608       {
609         temp[i] = annotations[i];
610       }
611     }
612
613     annotations = temp;
614   }
615
616   public void setAnnotationIndex(AlignmentAnnotation aa, int index)
617   {
618     if (aa == null || annotations == null || annotations.length - 1 < index)
619     {
620       return;
621     }
622
623     int aSize = annotations.length;
624     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize];
625
626     temp[index] = aa;
627
628     for (int i = 0; i < aSize; i++)
629     {
630       if (i == index)
631       {
632         continue;
633       }
634
635       if (i < index)
636       {
637         temp[i] = annotations[i];
638       }
639       else
640       {
641         temp[i] = annotations[i - 1];
642       }
643     }
644
645     annotations = temp;
646   }
647
648   /**
649    * DOCUMENT ME!
650    * 
651    * @return DOCUMENT ME!
652    */
653   public AlignmentAnnotation[] getAlignmentAnnotation()
654   {
655     return annotations;
656   }
657
658   public void setNucleotide(boolean b)
659   {
660     if (b)
661     {
662       type = NUCLEOTIDE;
663     }
664     else
665     {
666       type = PROTEIN;
667     }
668   }
669
670   public boolean isNucleotide()
671   {
672     if (type == NUCLEOTIDE)
673     {
674       return true;
675     }
676     else
677     {
678       return false;
679     }
680   }
681
682   public void setDataset(Alignment data)
683   {
684     if (dataset == null && data == null)
685     {
686       // Create a new dataset for this alignment.
687       // Can only be done once, if dataset is not null
688       // This will not be performed
689       SequenceI[] seqs = new SequenceI[getHeight()];
690       SequenceI currentSeq;
691       for (int i = 0; i < getHeight(); i++)
692       {
693         currentSeq = getSequenceAt(i);
694         if (currentSeq.getDatasetSequence() != null)
695         {
696           seqs[i] = (Sequence) currentSeq.getDatasetSequence();
697         }
698         else
699         {
700           seqs[i] = currentSeq.createDatasetSequence();
701         }
702       }
703
704       dataset = new Alignment(seqs);
705     }
706     else if (dataset == null && data != null)
707     {
708       dataset = data;
709     }
710     dataset.addAlignmentRef();
711   }
712
713   /**
714    * reference count for number of alignments referencing this one.
715    */
716   int alignmentRefs = 0;
717
718   /**
719    * increase reference count to this alignment.
720    */
721   private void addAlignmentRef()
722   {
723     alignmentRefs++;
724   }
725
726   public Alignment getDataset()
727   {
728     return dataset;
729   }
730
731   public boolean padGaps()
732   {
733     boolean modified = false;
734
735     // Remove excess gaps from the end of alignment
736     int maxLength = -1;
737
738     SequenceI current;
739     for (int i = 0; i < sequences.size(); i++)
740     {
741       current = getSequenceAt(i);
742       for (int j = current.getLength(); j > maxLength; j--)
743       {
744         if (j > maxLength
745                 && !jalview.util.Comparison.isGap(current.getCharAt(j)))
746         {
747           maxLength = j;
748           break;
749         }
750       }
751     }
752
753     maxLength++;
754
755     int cLength;
756     for (int i = 0; i < sequences.size(); i++)
757     {
758       current = getSequenceAt(i);
759       cLength = current.getLength();
760
761       if (cLength < maxLength)
762       {
763         current.insertCharAt(cLength, maxLength - cLength, gapCharacter);
764         modified = true;
765       }
766       else if (current.getLength() > maxLength)
767       {
768         current.deleteChars(maxLength, current.getLength());
769       }
770     }
771     return modified;
772   }
773
774   public HiddenSequences getHiddenSequences()
775   {
776     return hiddenSequences;
777   }
778
779   public CigarArray getCompactAlignment()
780   {
781     SeqCigar alseqs[] = new SeqCigar[sequences.size()];
782     for (int i = 0; i < sequences.size(); i++)
783     {
784       alseqs[i] = new SeqCigar((SequenceI) sequences.elementAt(i));
785     }
786     CigarArray cal = new CigarArray(alseqs);
787     cal.addOperation(CigarArray.M, getWidth());
788     return cal;
789   }
790
791   public void setProperty(Object key, Object value)
792   {
793     if (alignmentProperties == null)
794       alignmentProperties = new Hashtable();
795
796     alignmentProperties.put(key, value);
797   }
798
799   public Object getProperty(Object key)
800   {
801     if (alignmentProperties != null)
802       return alignmentProperties.get(key);
803     else
804       return null;
805   }
806
807   public Hashtable getProperties()
808   {
809     return alignmentProperties;
810   }
811
812   AlignedCodonFrame[] codonFrameList = null;
813
814   /*
815    * (non-Javadoc)
816    * 
817    * @see jalview.datamodel.AlignmentI#addCodonFrame(jalview.datamodel.AlignedCodonFrame)
818    */
819   public void addCodonFrame(AlignedCodonFrame codons)
820   {
821     if (codons == null)
822       return;
823     if (codonFrameList == null)
824     {
825       codonFrameList = new AlignedCodonFrame[]
826       { codons };
827       return;
828     }
829     AlignedCodonFrame[] t = new AlignedCodonFrame[codonFrameList.length + 1];
830     System.arraycopy(codonFrameList, 0, t, 0, codonFrameList.length);
831     t[codonFrameList.length] = codons;
832     codonFrameList = t;
833   }
834
835   /*
836    * (non-Javadoc)
837    * 
838    * @see jalview.datamodel.AlignmentI#getCodonFrame(int)
839    */
840   public AlignedCodonFrame getCodonFrame(int index)
841   {
842     return codonFrameList[index];
843   }
844
845   /*
846    * (non-Javadoc)
847    * 
848    * @see jalview.datamodel.AlignmentI#getCodonFrame(jalview.datamodel.SequenceI)
849    */
850   public AlignedCodonFrame[] getCodonFrame(SequenceI seq)
851   {
852     if (seq == null || codonFrameList == null)
853       return null;
854     Vector cframes = new Vector();
855     for (int f = 0; f < codonFrameList.length; f++)
856     {
857       if (codonFrameList[f].involvesSequence(seq))
858         cframes.addElement(codonFrameList[f]);
859     }
860     if (cframes.size() == 0)
861       return null;
862     AlignedCodonFrame[] cfr = new AlignedCodonFrame[cframes.size()];
863     cframes.copyInto(cfr);
864     return cfr;
865   }
866
867   /*
868    * (non-Javadoc)
869    * 
870    * @see jalview.datamodel.AlignmentI#getCodonFrames()
871    */
872   public AlignedCodonFrame[] getCodonFrames()
873   {
874     return codonFrameList;
875   }
876
877   /*
878    * (non-Javadoc)
879    * 
880    * @see jalview.datamodel.AlignmentI#removeCodonFrame(jalview.datamodel.AlignedCodonFrame)
881    */
882   public boolean removeCodonFrame(AlignedCodonFrame codons)
883   {
884     if (codons == null || codonFrameList == null)
885       return false;
886     boolean removed = false;
887     int i = 0, iSize = codonFrameList.length;
888     while (i < iSize)
889     {
890       if (codonFrameList[i] == codons)
891       {
892         removed = true;
893         if (i + 1 < iSize)
894         {
895           System.arraycopy(codonFrameList, i + 1, codonFrameList, i, iSize
896                   - i - 1);
897         }
898         iSize--;
899       }
900       else
901       {
902         i++;
903       }
904     }
905     return removed;
906   }
907 }