08c04f5b779f3b73c27cdba8baf081912a48d199
[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   /* (non-Javadoc)
445    * @see jalview.datamodel.AlignmentI#findIndex(jalview.datamodel.SequenceI)
446    */
447   public int findIndex(SequenceI s)
448   {
449     int i = 0;
450
451     while (i < sequences.size())
452     {
453       if (s == getSequenceAt(i))
454       {
455         return i;
456       }
457
458       i++;
459     }
460
461     return -1;
462   }
463   /* (non-Javadoc)
464    * @see jalview.datamodel.AlignmentI#findIndex(jalview.datamodel.SearchResults)
465    */
466   public int findIndex(SearchResults results)
467   {
468     int i=0;
469     
470     while (i<sequences.size())
471     {
472       if (results.involvesSequence(getSequenceAt(i)))
473       {
474         return i;
475       }
476       i++;
477     }
478     return -1;
479   }
480
481   /**
482    * DOCUMENT ME!
483    * 
484    * @return DOCUMENT ME!
485    */
486   public int getHeight()
487   {
488     return sequences.size();
489   }
490
491   /**
492    * DOCUMENT ME!
493    * 
494    * @return DOCUMENT ME!
495    */
496   public int getWidth()
497   {
498     int maxLength = -1;
499
500     for (int i = 0; i < sequences.size(); i++)
501     {
502       if (getSequenceAt(i).getLength() > maxLength)
503       {
504         maxLength = getSequenceAt(i).getLength();
505       }
506     }
507
508     return maxLength;
509   }
510
511   /**
512    * DOCUMENT ME!
513    * 
514    * @param gc
515    *                DOCUMENT ME!
516    */
517   public void setGapCharacter(char gc)
518   {
519     gapCharacter = gc;
520
521     for (int i = 0; i < sequences.size(); i++)
522     {
523       Sequence seq = (Sequence) sequences.elementAt(i);
524       seq.setSequence(seq.getSequenceAsString().replace('.', gc).replace(
525               '-', gc).replace(' ', gc));
526     }
527   }
528
529   /**
530    * DOCUMENT ME!
531    * 
532    * @return DOCUMENT ME!
533    */
534   public char getGapCharacter()
535   {
536     return gapCharacter;
537   }
538
539   /**
540    * DOCUMENT ME!
541    * 
542    * @return DOCUMENT ME!
543    */
544   public boolean isAligned()
545   {
546     int width = getWidth();
547
548     for (int i = 0; i < sequences.size(); i++)
549     {
550       if (getSequenceAt(i).getLength() != width)
551       {
552         return false;
553       }
554     }
555
556     return true;
557   }
558
559   /*
560    * (non-Javadoc)
561    * 
562    * @see jalview.datamodel.AlignmentI#deleteAnnotation(jalview.datamodel.AlignmentAnnotation)
563    */
564   public boolean deleteAnnotation(AlignmentAnnotation aa)
565   {
566     int aSize = 1;
567
568     if (annotations != null)
569     {
570       aSize = annotations.length;
571     }
572
573     if (aSize < 1)
574     {
575       return false;
576     }
577
578     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize - 1];
579
580     boolean swap = false;
581     int tIndex = 0;
582
583     for (int i = 0; i < aSize; i++)
584     {
585       if (annotations[i] == aa)
586       {
587         swap = true;
588         continue;
589       }
590       if (tIndex < temp.length)
591         temp[tIndex++] = annotations[i];
592     }
593
594     if (swap)
595     {
596       annotations = temp;
597       if (aa.sequenceRef != null)
598         aa.sequenceRef.removeAlignmentAnnotation(aa);
599     }
600     return swap;
601   }
602
603   /**
604    * DOCUMENT ME!
605    * 
606    * @param aa
607    *                DOCUMENT ME!
608    */
609   public void addAnnotation(AlignmentAnnotation aa)
610   {
611     int aSize = 1;
612     if (annotations != null)
613     {
614       aSize = annotations.length + 1;
615     }
616
617     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize];
618
619     temp[aSize - 1] = aa;
620
621     int i = 0;
622
623     if (aSize > 1)
624     {
625       for (i = 0; i < (aSize - 1); i++)
626       {
627         temp[i] = annotations[i];
628       }
629     }
630
631     annotations = temp;
632   }
633
634   public void setAnnotationIndex(AlignmentAnnotation aa, int index)
635   {
636     if (aa == null || annotations == null || annotations.length - 1 < index)
637     {
638       return;
639     }
640
641     int aSize = annotations.length;
642     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize];
643
644     temp[index] = aa;
645
646     for (int i = 0; i < aSize; i++)
647     {
648       if (i == index)
649       {
650         continue;
651       }
652
653       if (i < index)
654       {
655         temp[i] = annotations[i];
656       }
657       else
658       {
659         temp[i] = annotations[i - 1];
660       }
661     }
662
663     annotations = temp;
664   }
665
666   /**
667    * DOCUMENT ME!
668    * 
669    * @return DOCUMENT ME!
670    */
671   public AlignmentAnnotation[] getAlignmentAnnotation()
672   {
673     return annotations;
674   }
675
676   public void setNucleotide(boolean b)
677   {
678     if (b)
679     {
680       type = NUCLEOTIDE;
681     }
682     else
683     {
684       type = PROTEIN;
685     }
686   }
687
688   public boolean isNucleotide()
689   {
690     if (type == NUCLEOTIDE)
691     {
692       return true;
693     }
694     else
695     {
696       return false;
697     }
698   }
699
700   public void setDataset(Alignment data)
701   {
702     if (dataset == null && data == null)
703     {
704       // Create a new dataset for this alignment.
705       // Can only be done once, if dataset is not null
706       // This will not be performed
707       SequenceI[] seqs = new SequenceI[getHeight()];
708       SequenceI currentSeq;
709       for (int i = 0; i < getHeight(); i++)
710       {
711         currentSeq = getSequenceAt(i);
712         if (currentSeq.getDatasetSequence() != null)
713         {
714           seqs[i] = (Sequence) currentSeq.getDatasetSequence();
715         }
716         else
717         {
718           seqs[i] = currentSeq.createDatasetSequence();
719         }
720       }
721
722       dataset = new Alignment(seqs);
723     }
724     else if (dataset == null && data != null)
725     {
726       dataset = data;
727     }
728     dataset.addAlignmentRef();
729   }
730
731   /**
732    * reference count for number of alignments referencing this one.
733    */
734   int alignmentRefs = 0;
735
736   /**
737    * increase reference count to this alignment.
738    */
739   private void addAlignmentRef()
740   {
741     alignmentRefs++;
742   }
743
744   public Alignment getDataset()
745   {
746     return dataset;
747   }
748
749   public boolean padGaps()
750   {
751     boolean modified = false;
752
753     // Remove excess gaps from the end of alignment
754     int maxLength = -1;
755
756     SequenceI current;
757     for (int i = 0; i < sequences.size(); i++)
758     {
759       current = getSequenceAt(i);
760       for (int j = current.getLength(); j > maxLength; j--)
761       {
762         if (j > maxLength
763                 && !jalview.util.Comparison.isGap(current.getCharAt(j)))
764         {
765           maxLength = j;
766           break;
767         }
768       }
769     }
770
771     maxLength++;
772
773     int cLength;
774     for (int i = 0; i < sequences.size(); i++)
775     {
776       current = getSequenceAt(i);
777       cLength = current.getLength();
778
779       if (cLength < maxLength)
780       {
781         current.insertCharAt(cLength, maxLength - cLength, gapCharacter);
782         modified = true;
783       }
784       else if (current.getLength() > maxLength)
785       {
786         current.deleteChars(maxLength, current.getLength());
787       }
788     }
789     return modified;
790   }
791
792   public HiddenSequences getHiddenSequences()
793   {
794     return hiddenSequences;
795   }
796
797   public CigarArray getCompactAlignment()
798   {
799     SeqCigar alseqs[] = new SeqCigar[sequences.size()];
800     for (int i = 0; i < sequences.size(); i++)
801     {
802       alseqs[i] = new SeqCigar((SequenceI) sequences.elementAt(i));
803     }
804     CigarArray cal = new CigarArray(alseqs);
805     cal.addOperation(CigarArray.M, getWidth());
806     return cal;
807   }
808
809   public void setProperty(Object key, Object value)
810   {
811     if (alignmentProperties == null)
812       alignmentProperties = new Hashtable();
813
814     alignmentProperties.put(key, value);
815   }
816
817   public Object getProperty(Object key)
818   {
819     if (alignmentProperties != null)
820       return alignmentProperties.get(key);
821     else
822       return null;
823   }
824
825   public Hashtable getProperties()
826   {
827     return alignmentProperties;
828   }
829
830   AlignedCodonFrame[] codonFrameList = null;
831
832   /*
833    * (non-Javadoc)
834    * 
835    * @see jalview.datamodel.AlignmentI#addCodonFrame(jalview.datamodel.AlignedCodonFrame)
836    */
837   public void addCodonFrame(AlignedCodonFrame codons)
838   {
839     if (codons == null)
840       return;
841     if (codonFrameList == null)
842     {
843       codonFrameList = new AlignedCodonFrame[]
844       { codons };
845       return;
846     }
847     AlignedCodonFrame[] t = new AlignedCodonFrame[codonFrameList.length + 1];
848     System.arraycopy(codonFrameList, 0, t, 0, codonFrameList.length);
849     t[codonFrameList.length] = codons;
850     codonFrameList = t;
851   }
852
853   /*
854    * (non-Javadoc)
855    * 
856    * @see jalview.datamodel.AlignmentI#getCodonFrame(int)
857    */
858   public AlignedCodonFrame getCodonFrame(int index)
859   {
860     return codonFrameList[index];
861   }
862
863   /*
864    * (non-Javadoc)
865    * 
866    * @see jalview.datamodel.AlignmentI#getCodonFrame(jalview.datamodel.SequenceI)
867    */
868   public AlignedCodonFrame[] getCodonFrame(SequenceI seq)
869   {
870     if (seq == null || codonFrameList == null)
871       return null;
872     Vector cframes = new Vector();
873     for (int f = 0; f < codonFrameList.length; f++)
874     {
875       if (codonFrameList[f].involvesSequence(seq))
876         cframes.addElement(codonFrameList[f]);
877     }
878     if (cframes.size() == 0)
879       return null;
880     AlignedCodonFrame[] cfr = new AlignedCodonFrame[cframes.size()];
881     cframes.copyInto(cfr);
882     return cfr;
883   }
884
885   /*
886    * (non-Javadoc)
887    * 
888    * @see jalview.datamodel.AlignmentI#getCodonFrames()
889    */
890   public AlignedCodonFrame[] getCodonFrames()
891   {
892     return codonFrameList;
893   }
894
895   /*
896    * (non-Javadoc)
897    * 
898    * @see jalview.datamodel.AlignmentI#removeCodonFrame(jalview.datamodel.AlignedCodonFrame)
899    */
900   public boolean removeCodonFrame(AlignedCodonFrame codons)
901   {
902     if (codons == null || codonFrameList == null)
903       return false;
904     boolean removed = false;
905     int i = 0, iSize = codonFrameList.length;
906     while (i < iSize)
907     {
908       if (codonFrameList[i] == codons)
909       {
910         removed = true;
911         if (i + 1 < iSize)
912         {
913           System.arraycopy(codonFrameList, i + 1, codonFrameList, i, iSize
914                   - i - 1);
915         }
916         iSize--;
917       }
918       else
919       {
920         i++;
921       }
922     }
923     return removed;
924   }
925
926 }