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