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