case invariant matching of seqeunce feature to sequence id string
[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
348     int i = 0;
349     SequenceI sq=null;
350     String sqname=null;
351     while (i < sequences.size())
352     {
353       sq = getSequenceAt(i);
354       sqname = sq.getName();
355       if (sqname.equals(token) // exact match
356           || (b && // allow imperfect matches - case varies 
357               (sqname.equalsIgnoreCase(token))))
358       {
359         return getSequenceAt(i);
360       }
361
362       i++;
363     }
364
365     return null;
366   }
367   public SequenceI[] findSequenceMatch(String name)
368   {
369     Vector matches = new Vector();
370     int i = 0;
371
372     while (i < sequences.size())
373     {
374       if (getSequenceAt(i).getName().equals(name))
375       {
376         matches.addElement(getSequenceAt(i));
377       }
378       i++;
379     }
380
381     SequenceI[] result = new SequenceI[matches.size()];
382     for (i = 0; i < result.length; i++)
383     {
384       result[i] = (SequenceI) matches.elementAt(i);
385     }
386
387     return result;
388
389   }
390
391   /**    */
392   public int findIndex(SequenceI s)
393   {
394     int i = 0;
395
396     while (i < sequences.size())
397     {
398       if (s == getSequenceAt(i))
399       {
400         return i;
401       }
402
403       i++;
404     }
405
406     return -1;
407   }
408
409   /**
410    * DOCUMENT ME!
411    *
412    * @return DOCUMENT ME!
413    */
414   public int getHeight()
415   {
416     return sequences.size();
417   }
418
419   /**
420    * DOCUMENT ME!
421    *
422    * @return DOCUMENT ME!
423    */
424   public int getWidth()
425   {
426     int maxLength = -1;
427
428     for (int i = 0; i < sequences.size(); i++)
429     {
430       if (getSequenceAt(i).getLength() > maxLength)
431       {
432         maxLength = getSequenceAt(i).getLength();
433       }
434     }
435
436     return maxLength;
437   }
438
439   /**
440    * DOCUMENT ME!
441    *
442    * @param gc DOCUMENT ME!
443    */
444   public void setGapCharacter(char gc)
445   {
446     gapCharacter = gc;
447
448     for (int i = 0; i < sequences.size(); i++)
449     {
450       Sequence seq = (Sequence) sequences.elementAt(i);
451       seq.setSequence(seq.getSequenceAsString()
452                       .replace('.', gc)
453                       .replace('-', gc)
454                       .replace(' ', gc)
455           );
456     }
457   }
458
459   /**
460    * DOCUMENT ME!
461    *
462    * @return DOCUMENT ME!
463    */
464   public char getGapCharacter()
465   {
466     return gapCharacter;
467   }
468
469   /**
470    * DOCUMENT ME!
471    *
472    * @return DOCUMENT ME!
473    */
474   public boolean isAligned()
475   {
476     int width = getWidth();
477
478     for (int i = 0; i < sequences.size(); i++)
479     {
480       if (getSequenceAt(i).getLength() != width)
481       {
482         return false;
483       }
484     }
485
486     return true;
487   }
488   /* (non-Javadoc)
489    * @see jalview.datamodel.AlignmentI#deleteAnnotation(jalview.datamodel.AlignmentAnnotation)
490    */
491   public boolean deleteAnnotation(AlignmentAnnotation aa)
492   {
493     int aSize = 1;
494
495     if (annotations != null)
496     {
497       aSize = annotations.length;
498     }
499
500     if (aSize < 1)
501     {
502       return false;
503     }
504
505     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize - 1];
506
507     boolean swap=false;
508     int tIndex = 0;
509
510     for (int i = 0; i < aSize; i++)
511     {
512       if (annotations[i] == aa)
513       {
514         swap=true;
515         continue;
516       }
517       if (tIndex<temp.length)
518         temp[tIndex++] = annotations[i];
519     }
520
521     if (swap)
522     {
523       annotations = temp;
524       if(aa.sequenceRef!=null)
525         aa.sequenceRef.removeAlignmentAnnotation(aa);
526     }
527     return swap;
528   }
529
530   /**
531    * DOCUMENT ME!
532    *
533    * @param aa DOCUMENT ME!
534    */
535   public void addAnnotation(AlignmentAnnotation aa)
536   {
537     int aSize = 1;
538     if (annotations != null)
539     {
540       aSize = annotations.length + 1;
541     }
542
543     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize];
544
545     temp[aSize - 1] = aa;
546
547     int i = 0;
548
549     if (aSize > 1)
550     {
551       for (i = 0; i < (aSize - 1); i++)
552       {
553         temp[i] = annotations[i];
554       }
555     }
556
557     annotations = temp;
558   }
559
560   public void setAnnotationIndex(AlignmentAnnotation aa, int index)
561   {
562     if (aa == null || annotations == null || annotations.length - 1 < index)
563     {
564       return;
565     }
566
567     int aSize = annotations.length;
568     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize];
569
570     temp[index] = aa;
571
572     for (int i = 0; i < aSize; i++)
573     {
574       if (i == index)
575       {
576         continue;
577       }
578
579       if (i < index)
580       {
581         temp[i] = annotations[i];
582       }
583       else
584       {
585         temp[i] = annotations[i - 1];
586       }
587     }
588
589     annotations = temp;
590   }
591
592   /**
593    * DOCUMENT ME!
594    *
595    * @return DOCUMENT ME!
596    */
597   public AlignmentAnnotation[] getAlignmentAnnotation()
598   {
599     return annotations;
600   }
601
602   public void setNucleotide(boolean b)
603   {
604     if (b)
605     {
606       type = NUCLEOTIDE;
607     }
608     else
609     {
610       type = PROTEIN;
611     }
612   }
613
614   public boolean isNucleotide()
615   {
616     if (type == NUCLEOTIDE)
617     {
618       return true;
619     }
620     else
621     {
622       return false;
623     }
624   }
625
626   public void setDataset(Alignment data)
627   {
628     if (dataset == null && data == null)
629     {
630       // Create a new dataset for this alignment.
631       // Can only be done once, if dataset is not null
632       // This will not be performed
633       SequenceI[] seqs = new SequenceI[getHeight()];
634       SequenceI currentSeq;
635       for (int i = 0; i < getHeight(); i++)
636       {
637         currentSeq = getSequenceAt(i);
638         if (currentSeq.getDatasetSequence() != null)
639         {
640           seqs[i] = (Sequence) currentSeq.getDatasetSequence();
641         }
642         else
643         {
644           seqs[i] = currentSeq.createDatasetSequence();
645         }
646       }
647
648       dataset = new Alignment(seqs);
649     }
650     else if (dataset == null && data != null)
651     {
652       dataset = data;
653     }
654     dataset.addAlignmentRef();
655   }
656   /**
657    * reference count for number of alignments referencing this one.
658    */
659   int alignmentRefs=0;
660   /**
661    * increase reference count to this alignment.
662    */
663   private void addAlignmentRef()
664   {
665     alignmentRefs++;
666   }
667
668   public Alignment getDataset()
669   {
670     return dataset;
671   }
672
673   public boolean padGaps()
674   {
675     boolean modified = false;
676
677     //Remove excess gaps from the end of alignment
678     int maxLength = -1;
679
680     SequenceI current;
681     for (int i = 0; i < sequences.size(); i++)
682     {
683       current = getSequenceAt(i);
684       for (int j = current.getLength(); j > maxLength; j--)
685       {
686         if (j > maxLength && !jalview.util.Comparison.isGap(
687             current.getCharAt(j)))
688         {
689           maxLength = j;
690           break;
691         }
692       }
693     }
694
695     maxLength++;
696
697     int cLength;
698     for (int i = 0; i < sequences.size();
699          i++)
700     {
701       current = getSequenceAt(i);
702       cLength = current.getLength();
703
704       if (cLength < maxLength)
705       {
706         current.insertCharAt(cLength,
707                              maxLength - cLength, gapCharacter);
708         modified = true;
709       }
710       else if (current.getLength() > maxLength)
711       {
712         current.deleteChars(maxLength, current.getLength());
713       }
714     }
715     return modified;
716   }
717
718   public HiddenSequences getHiddenSequences()
719   {
720     return hiddenSequences;
721   }
722
723   public CigarArray getCompactAlignment()
724   {
725     SeqCigar alseqs[] = new SeqCigar[sequences.size()];
726     for (int i = 0; i < sequences.size(); i++)
727     {
728       alseqs[i] = new SeqCigar( (SequenceI) sequences.elementAt(i));
729     }
730     CigarArray cal = new CigarArray(alseqs);
731     cal.addOperation(CigarArray.M, getWidth());
732     return cal;
733   }
734
735   public void setProperty(Object key, Object value)
736   {
737     if(alignmentProperties==null)
738       alignmentProperties = new Hashtable();
739
740     alignmentProperties.put(key,value);
741   }
742
743   public Object getProperty(Object key)
744   {
745     if(alignmentProperties!=null)
746       return alignmentProperties.get(key);
747     else
748       return null;
749   }
750
751   public Hashtable getProperties()
752   {
753     return alignmentProperties;
754   }
755   AlignedCodonFrame[] codonFrameList=null;
756   /* (non-Javadoc)
757    * @see jalview.datamodel.AlignmentI#addCodonFrame(jalview.datamodel.AlignedCodonFrame)
758    */
759   public void addCodonFrame(AlignedCodonFrame codons)
760   {
761     if (codons==null)
762       return;
763     if (codonFrameList==null)
764     {
765       codonFrameList = new AlignedCodonFrame[] { codons };
766       return;
767     }
768     AlignedCodonFrame[] t = new AlignedCodonFrame[codonFrameList.length+1];
769     System.arraycopy(codonFrameList, 0, t, 0, codonFrameList.length);
770     t[codonFrameList.length] = codons;
771     codonFrameList = t;
772   }
773
774   /* (non-Javadoc)
775    * @see jalview.datamodel.AlignmentI#getCodonFrame(int)
776    */
777   public AlignedCodonFrame getCodonFrame(int index)
778   {
779     return codonFrameList[index];
780   }
781
782   /* (non-Javadoc)
783    * @see jalview.datamodel.AlignmentI#getCodonFrame(jalview.datamodel.SequenceI)
784    */
785   public AlignedCodonFrame[] getCodonFrame(SequenceI seq)
786   {
787     if (seq==null || codonFrameList==null)
788       return null;
789     Vector cframes=new Vector();
790     for (int f=0;f<codonFrameList.length; f++)
791     {
792       if (codonFrameList[f].involvesSequence(seq))
793         cframes.addElement(codonFrameList[f]);
794     }
795     if (cframes.size()==0)
796       return null;
797     AlignedCodonFrame[] cfr = new AlignedCodonFrame[cframes.size()];
798     cframes.copyInto(cfr);
799     return cfr;
800   }
801
802   /* (non-Javadoc)
803    * @see jalview.datamodel.AlignmentI#getCodonFrames()
804    */
805   public AlignedCodonFrame[] getCodonFrames()
806   {
807     return codonFrameList;
808   }
809
810   /* (non-Javadoc)
811    * @see jalview.datamodel.AlignmentI#removeCodonFrame(jalview.datamodel.AlignedCodonFrame)
812    */
813   public boolean removeCodonFrame(AlignedCodonFrame codons)
814   {
815     if (codons==null || codonFrameList==null)
816       return false;
817     boolean removed=false;
818     int i=0,iSize=codonFrameList.length;
819     while (i<iSize)
820     {
821       if (codonFrameList[i]==codons)
822       {
823         removed=true;
824         if (i+1<iSize)
825         {
826           System.arraycopy(codonFrameList,i+1,codonFrameList, i, iSize-i-1);
827         }
828         iSize--;
829       }
830       else
831       {
832         i++;
833       }
834     }
835     return removed;
836   }
837 }