implement new insert annotation row method and ensure group annotation rows are remov...
[jalview.git] / src / jalview / datamodel / Alignment.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Development Version 2.4.1)
3  * Copyright (C) 2009 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 /**
29  * @author JimP
30  *
31  */
32 public class Alignment implements AlignmentI
33 {
34   protected Alignment dataset;
35
36   protected Vector sequences;
37
38   protected Vector groups = new Vector();
39
40   protected char gapCharacter = '-';
41
42   protected int type = NUCLEOTIDE;
43
44   public static final int PROTEIN = 0;
45
46   public static final int NUCLEOTIDE = 1;
47
48   /** DOCUMENT ME!! */
49   public AlignmentAnnotation[] annotations;
50
51   HiddenSequences hiddenSequences = new HiddenSequences(this);
52
53   public Hashtable alignmentProperties;
54
55   private void initAlignment(SequenceI[] seqs)
56   {
57     int i = 0;
58
59     if (jalview.util.Comparison.isNucleotide(seqs))
60     {
61       type = NUCLEOTIDE;
62     }
63     else
64     {
65       type = PROTEIN;
66     }
67
68     sequences = new Vector();
69
70     for (i = 0; i < seqs.length; i++)
71     {
72       sequences.addElement(seqs[i]);
73     }
74
75   }
76
77   /**
78    * Make an alignment from an array of Sequences.
79    * 
80    * @param sequences
81    */
82   public Alignment(SequenceI[] seqs)
83   {
84     initAlignment(seqs);
85   }
86
87   /**
88    * Make a new alignment from an array of SeqCigars
89    * 
90    * @param seqs
91    *                SeqCigar[]
92    */
93   public Alignment(SeqCigar[] alseqs)
94   {
95     SequenceI[] seqs = SeqCigar.createAlignmentSequences(alseqs,
96             gapCharacter, new ColumnSelection(), null);
97     initAlignment(seqs);
98   }
99
100   /**
101    * Make a new alignment from an CigarArray JBPNote - can only do this when
102    * compactAlignment does not contain hidden regions. JBPNote - must also check
103    * that compactAlignment resolves to a set of SeqCigars - or construct them
104    * appropriately.
105    * 
106    * @param compactAlignment
107    *                CigarArray
108    */
109   public static AlignmentI createAlignment(CigarArray compactAlignment)
110   {
111     throw new Error("Alignment(CigarArray) not yet implemented");
112     // this(compactAlignment.refCigars);
113   }
114
115   /**
116    * DOCUMENT ME!
117    * 
118    * @return DOCUMENT ME!
119    */
120   public Vector getSequences()
121   {
122     return sequences;
123   }
124
125   public SequenceI[] getSequencesArray()
126   {
127     if (sequences == null)
128       return null;
129     SequenceI[] reply = new SequenceI[sequences.size()];
130     for (int i = 0; i < sequences.size(); i++)
131     {
132       reply[i] = (SequenceI) sequences.elementAt(i);
133     }
134     return reply;
135   }
136
137   /**
138    * DOCUMENT ME!
139    * 
140    * @param i
141    *                DOCUMENT ME!
142    * 
143    * @return DOCUMENT ME!
144    */
145   public SequenceI getSequenceAt(int i)
146   {
147     if (i < sequences.size())
148     {
149       return (SequenceI) sequences.elementAt(i);
150     }
151
152     return null;
153   }
154
155   /**
156    * Adds a sequence to the alignment. Recalculates maxLength and size.
157    * 
158    * @param snew
159    */
160   public void addSequence(SequenceI snew)
161   {
162     if (dataset != null)
163     {
164       // maintain dataset integrity
165       if (snew.getDatasetSequence() != null)
166       {
167         getDataset().addSequence(snew.getDatasetSequence());
168       }
169       else
170       {
171         // derive new sequence
172         SequenceI adding = snew.deriveSequence();
173         getDataset().addSequence(adding.getDatasetSequence());
174         snew = adding;
175       }
176     }
177     if (sequences == null)
178     {
179       initAlignment(new SequenceI[]
180       { snew });
181     }
182     else
183     {
184       sequences.addElement(snew);
185     }
186     if (hiddenSequences != null)
187       hiddenSequences.adjustHeightSequenceAdded();
188   }
189
190   /**
191    * Adds a sequence to the alignment. Recalculates maxLength and size.
192    * 
193    * @param snew
194    */
195   public void setSequenceAt(int i, SequenceI snew)
196   {
197     SequenceI oldseq = getSequenceAt(i);
198     deleteSequence(oldseq);
199
200     sequences.setElementAt(snew, i);
201   }
202
203   /**
204    * DOCUMENT ME!
205    * 
206    * @return DOCUMENT ME!
207    */
208   public Vector getGroups()
209   {
210     return groups;
211   }
212
213   public void finalize()
214   {
215     if (getDataset() != null)
216       getDataset().removeAlignmentRef();
217
218     dataset = null;
219     sequences = null;
220     groups = null;
221     annotations = null;
222     hiddenSequences = null;
223   }
224
225   /**
226    * decrement the alignmentRefs counter by one and call finalize if it goes to
227    * zero.
228    */
229   private void removeAlignmentRef()
230   {
231     if (--alignmentRefs == 0)
232     {
233       finalize();
234     }
235   }
236
237   /**
238    * DOCUMENT ME!
239    * 
240    * @param s
241    *                DOCUMENT ME!
242    */
243   public void deleteSequence(SequenceI s)
244   {
245     deleteSequence(findIndex(s));
246   }
247
248   /**
249    * DOCUMENT ME!
250    * 
251    * @param i
252    *                DOCUMENT ME!
253    */
254   public void deleteSequence(int i)
255   {
256     if (i > -1 && i < getHeight())
257     {
258       sequences.removeElementAt(i);
259       hiddenSequences.adjustHeightSequenceDeleted(i);
260     }
261   }
262
263   /**    */
264   public SequenceGroup findGroup(SequenceI s)
265   {
266     for (int i = 0; i < this.groups.size(); i++)
267     {
268       SequenceGroup sg = (SequenceGroup) groups.elementAt(i);
269
270       if (sg.getSequences(null).contains(s))
271       {
272         return sg;
273       }
274     }
275
276     return null;
277   }
278
279   /**
280    * DOCUMENT ME!
281    * 
282    * @param s
283    *                DOCUMENT ME!
284    * 
285    * @return DOCUMENT ME!
286    */
287   public SequenceGroup[] findAllGroups(SequenceI s)
288   {
289     Vector temp = new Vector();
290
291     int gSize = groups.size();
292     for (int i = 0; i < gSize; i++)
293     {
294       SequenceGroup sg = (SequenceGroup) groups.elementAt(i);
295       if (sg == null || sg.getSequences(null) == null)
296       {
297         this.deleteGroup(sg);
298         gSize--;
299         continue;
300       }
301
302       if (sg.getSequences(null).contains(s))
303       {
304         temp.addElement(sg);
305       }
306     }
307
308     SequenceGroup[] ret = new SequenceGroup[temp.size()];
309
310     for (int i = 0; i < temp.size(); i++)
311     {
312       ret[i] = (SequenceGroup) temp.elementAt(i);
313     }
314
315     return ret;
316   }
317
318   /**    */
319   public void addGroup(SequenceGroup sg)
320   {
321     if (!groups.contains(sg))
322     {
323       if (hiddenSequences.getSize() > 0)
324       {
325         int i, iSize = sg.getSize();
326         for (i = 0; i < iSize; i++)
327         {
328           if (!sequences.contains(sg.getSequenceAt(i)))
329           {
330             sg.deleteSequence(sg.getSequenceAt(i), false);
331             iSize--;
332             i--;
333           }
334         }
335
336         if (sg.getSize() < 1)
337         {
338           return;
339         }
340       }
341
342       groups.addElement(sg);
343     }
344   }
345
346   /**
347    * DOCUMENT ME!
348    */
349   public void deleteAllGroups()
350   {
351     groups.removeAllElements();
352   }
353
354   /**    */
355   public void deleteGroup(SequenceGroup g)
356   {
357     if (groups.contains(g))
358     {
359       if (annotations!=null && annotations.length>0)
360       {
361         if (g.hasAnnotationRows())
362         {  // remove any annotation references. 
363           deleteAnnotation(g.getConsensus());// todo - create=false flag so we don't create another object unnecessarily
364           deleteAnnotation(g.getConservationRow());
365         }
366       }
367       groups.removeElement(g);
368     }
369   }
370
371   /**    */
372   public SequenceI findName(String name)
373   {
374     return findName(name, false);
375   }
376
377   /*
378    * (non-Javadoc)
379    * 
380    * @see jalview.datamodel.AlignmentI#findName(java.lang.String, boolean)
381    */
382   public SequenceI findName(String token, boolean b)
383   {
384     return findName(null, token, b);
385   }
386
387   /*
388    * (non-Javadoc)
389    * 
390    * @see jalview.datamodel.AlignmentI#findName(SequenceI, java.lang.String,
391    *      boolean)
392    */
393   public SequenceI findName(SequenceI startAfter, String token, boolean b)
394   {
395
396     int i = 0;
397     SequenceI sq = null;
398     String sqname = null;
399     if (startAfter != null)
400     {
401       // try to find the sequence in the alignment
402       boolean matched = false;
403       while (i < sequences.size())
404       {
405         if (getSequenceAt(i++) == startAfter)
406         {
407           matched = true;
408           break;
409         }
410       }
411       if (!matched)
412       {
413         i = 0;
414       }
415     }
416     while (i < sequences.size())
417     {
418       sq = getSequenceAt(i);
419       sqname = sq.getName();
420       if (sqname.equals(token) // exact match
421               || (b && // allow imperfect matches - case varies
422               (sqname.equalsIgnoreCase(token))))
423       {
424         return getSequenceAt(i);
425       }
426
427       i++;
428     }
429
430     return null;
431   }
432
433   public SequenceI[] findSequenceMatch(String name)
434   {
435     Vector matches = new Vector();
436     int i = 0;
437
438     while (i < sequences.size())
439     {
440       if (getSequenceAt(i).getName().equals(name))
441       {
442         matches.addElement(getSequenceAt(i));
443       }
444       i++;
445     }
446
447     SequenceI[] result = new SequenceI[matches.size()];
448     for (i = 0; i < result.length; i++)
449     {
450       result[i] = (SequenceI) matches.elementAt(i);
451     }
452
453     return result;
454
455   }
456
457   /*
458    * (non-Javadoc)
459    * 
460    * @see jalview.datamodel.AlignmentI#findIndex(jalview.datamodel.SequenceI)
461    */
462   public int findIndex(SequenceI s)
463   {
464     int i = 0;
465
466     while (i < sequences.size())
467     {
468       if (s == getSequenceAt(i))
469       {
470         return i;
471       }
472
473       i++;
474     }
475
476     return -1;
477   }
478
479   /*
480    * (non-Javadoc)
481    * 
482    * @see jalview.datamodel.AlignmentI#findIndex(jalview.datamodel.SearchResults)
483    */
484   public int findIndex(SearchResults results)
485   {
486     int i = 0;
487
488     while (i < sequences.size())
489     {
490       if (results.involvesSequence(getSequenceAt(i)))
491       {
492         return i;
493       }
494       i++;
495     }
496     return -1;
497   }
498
499   /**
500    * DOCUMENT ME!
501    * 
502    * @return DOCUMENT ME!
503    */
504   public int getHeight()
505   {
506     return sequences.size();
507   }
508
509   /**
510    * DOCUMENT ME!
511    * 
512    * @return DOCUMENT ME!
513    */
514   public int getWidth()
515   {
516     int maxLength = -1;
517
518     for (int i = 0; i < sequences.size(); i++)
519     {
520       if (getSequenceAt(i).getLength() > maxLength)
521       {
522         maxLength = getSequenceAt(i).getLength();
523       }
524     }
525
526     return maxLength;
527   }
528
529   /**
530    * DOCUMENT ME!
531    * 
532    * @param gc
533    *                DOCUMENT ME!
534    */
535   public void setGapCharacter(char gc)
536   {
537     gapCharacter = gc;
538
539     for (int i = 0; i < sequences.size(); i++)
540     {
541       Sequence seq = (Sequence) sequences.elementAt(i);
542       seq.setSequence(seq.getSequenceAsString().replace('.', gc).replace(
543               '-', gc).replace(' ', gc));
544     }
545   }
546
547   /**
548    * DOCUMENT ME!
549    * 
550    * @return DOCUMENT ME!
551    */
552   public char getGapCharacter()
553   {
554     return gapCharacter;
555   }
556
557   /**
558    * DOCUMENT ME!
559    * 
560    * @return DOCUMENT ME!
561    */
562   public boolean isAligned()
563   {
564     int width = getWidth();
565
566     for (int i = 0; i < sequences.size(); i++)
567     {
568       if (getSequenceAt(i).getLength() != width)
569       {
570         return false;
571       }
572     }
573
574     return true;
575   }
576
577   /*
578    * (non-Javadoc)
579    * 
580    * @see jalview.datamodel.AlignmentI#deleteAnnotation(jalview.datamodel.AlignmentAnnotation)
581    */
582   public boolean deleteAnnotation(AlignmentAnnotation aa)
583   {
584     int aSize = 1;
585
586     if (annotations != null)
587     {
588       aSize = annotations.length;
589     }
590
591     if (aSize < 1)
592     {
593       return false;
594     }
595
596     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize - 1];
597
598     boolean swap = false;
599     int tIndex = 0;
600
601     for (int i = 0; i < aSize; i++)
602     {
603       if (annotations[i] == aa)
604       {
605         swap = true;
606         continue;
607       }
608       if (tIndex < temp.length)
609         temp[tIndex++] = annotations[i];
610     }
611
612     if (swap)
613     {
614       annotations = temp;
615       if (aa.sequenceRef != null)
616         aa.sequenceRef.removeAlignmentAnnotation(aa);
617     }
618     return swap;
619   }
620
621   /* (non-Javadoc)
622    * @see jalview.datamodel.AlignmentI#addAnnotation(jalview.datamodel.AlignmentAnnotation)
623    */
624   public void addAnnotation(AlignmentAnnotation aa)
625   {
626     addAnnotation(aa, -1);
627   }
628   
629   /* (non-Javadoc)
630    * @see jalview.datamodel.AlignmentI#addAnnotation(jalview.datamodel.AlignmentAnnotation, int)
631    */
632   public void addAnnotation(AlignmentAnnotation aa, int pos)
633   {
634     int aSize = 1;
635     if (annotations != null)
636     {
637       aSize = annotations.length + 1;
638     }
639
640     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize];
641     int i = 0;
642     if (pos==-1 || pos>=aSize) {
643       temp[aSize - 1] = aa;
644     } else {
645       temp[pos] = aa;
646     }
647     if (aSize > 1)
648     {
649       int p=0;
650       for (i = 0; i < (aSize-1); i++,p++)
651       {
652         if (p==pos)
653         {
654           p++;
655         }
656         if (p<temp.length) {
657           temp[p] = annotations[i];
658         }
659       }
660     }
661
662     annotations = temp;
663   }
664
665   public void setAnnotationIndex(AlignmentAnnotation aa, int index)
666   {
667     if (aa == null || annotations == null || annotations.length - 1 < index)
668     {
669       return;
670     }
671
672     int aSize = annotations.length;
673     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize];
674
675     temp[index] = aa;
676
677     for (int i = 0; i < aSize; i++)
678     {
679       if (i == index)
680       {
681         continue;
682       }
683
684       if (i < index)
685       {
686         temp[i] = annotations[i];
687       }
688       else
689       {
690         temp[i] = annotations[i - 1];
691       }
692     }
693
694     annotations = temp;
695   }
696
697   /**
698    * DOCUMENT ME!
699    * 
700    * @return DOCUMENT ME!
701    */
702   public AlignmentAnnotation[] getAlignmentAnnotation()
703   {
704     return annotations;
705   }
706
707   public void setNucleotide(boolean b)
708   {
709     if (b)
710     {
711       type = NUCLEOTIDE;
712     }
713     else
714     {
715       type = PROTEIN;
716     }
717   }
718
719   public boolean isNucleotide()
720   {
721     if (type == NUCLEOTIDE)
722     {
723       return true;
724     }
725     else
726     {
727       return false;
728     }
729   }
730
731   public void setDataset(Alignment data)
732   {
733     if (dataset == null && data == null)
734     {
735       // Create a new dataset for this alignment.
736       // Can only be done once, if dataset is not null
737       // This will not be performed
738       SequenceI[] seqs = new SequenceI[getHeight()];
739       SequenceI currentSeq;
740       for (int i = 0; i < getHeight(); i++)
741       {
742         currentSeq = getSequenceAt(i);
743         if (currentSeq.getDatasetSequence() != null)
744         {
745           seqs[i] = (Sequence) currentSeq.getDatasetSequence();
746         }
747         else
748         {
749           seqs[i] = currentSeq.createDatasetSequence();
750         }
751       }
752
753       dataset = new Alignment(seqs);
754     }
755     else if (dataset == null && data != null)
756     {
757       dataset = data;
758     }
759     dataset.addAlignmentRef();
760   }
761
762   /**
763    * reference count for number of alignments referencing this one.
764    */
765   int alignmentRefs = 0;
766
767   /**
768    * increase reference count to this alignment.
769    */
770   private void addAlignmentRef()
771   {
772     alignmentRefs++;
773   }
774
775   public Alignment getDataset()
776   {
777     return dataset;
778   }
779
780   public boolean padGaps()
781   {
782     boolean modified = false;
783
784     // Remove excess gaps from the end of alignment
785     int maxLength = -1;
786
787     SequenceI current;
788     for (int i = 0; i < sequences.size(); i++)
789     {
790       current = getSequenceAt(i);
791       for (int j = current.getLength(); j > maxLength; j--)
792       {
793         if (j > maxLength
794                 && !jalview.util.Comparison.isGap(current.getCharAt(j)))
795         {
796           maxLength = j;
797           break;
798         }
799       }
800     }
801
802     maxLength++;
803
804     int cLength;
805     for (int i = 0; i < sequences.size(); i++)
806     {
807       current = getSequenceAt(i);
808       cLength = current.getLength();
809
810       if (cLength < maxLength)
811       {
812         current.insertCharAt(cLength, maxLength - cLength, gapCharacter);
813         modified = true;
814       }
815       else if (current.getLength() > maxLength)
816       {
817         current.deleteChars(maxLength, current.getLength());
818       }
819     }
820     return modified;
821   }
822   /**
823    * Justify the sequences to the left or right by deleting and inserting gaps before the initial residue or after the terminal residue
824    * @param right true if alignment padded to right, false to justify to left
825    * @return true if alignment was changed
826    */
827   public boolean justify(boolean right)
828   {
829     boolean modified = false;
830
831     // Remove excess gaps from the end of alignment
832     int maxLength = -1;
833     int ends[] = new int[sequences.size()*2];
834     SequenceI current;
835     for (int i = 0; i < sequences.size(); i++)
836     {
837       current = getSequenceAt(i);
838       // This should really be a sequence method
839       ends[i*2] = current.findIndex(current.getStart());
840       ends[i*2+1] = current.findIndex(current.getStart()+current.getLength());
841       boolean hitres=false;
842       for (int j = 0,rs=0,ssiz=current.getLength(); j<ssiz; j++)
843       {
844         if (!jalview.util.Comparison.isGap(current.getCharAt(j)))
845         {
846           if (!hitres)
847           {
848             ends[i*2] = j;
849             hitres=true;
850           } else {
851             ends[i*2+1] = j;
852             if (j-ends[i*2]>maxLength)
853             {
854               maxLength = j-ends[i*2];
855             }
856           }
857         }
858       }
859     }
860
861     maxLength++;
862     // now edit the flanking gaps to justify to either left or right
863     int cLength,extent,diff;
864     for (int i = 0; i < sequences.size(); i++)
865     {
866       current = getSequenceAt(i);
867       
868       cLength = 1+ends[i*2+1]-ends[i*2];
869       diff = maxLength-cLength; // number of gaps to indent
870       extent = current.getLength();
871       if (right)
872       {
873         // right justify
874         if (extent>ends[i*2+1])
875         {
876           current.deleteChars(ends[i*2+1]+1, extent);
877           modified = true;
878         }
879         if (ends[i*2]>diff)
880         {
881           current.deleteChars(0, ends[i*2]-diff);
882           modified = true;
883         } else {
884           if (ends[i*2]<diff)
885           {
886             current.insertCharAt(0, diff-ends[i*2],gapCharacter);
887             modified = true;
888           }
889         }
890       } else {
891         // left justify
892         if (ends[i*2]>0)
893         {
894           current.deleteChars(0, ends[i*2]);
895           modified = true;
896           ends[i*2+1]-=ends[i*2];
897           extent-=ends[i*2];
898         }
899         if (extent>maxLength)
900         {
901           current.deleteChars(maxLength+1, extent);
902           modified = true;
903         } else {
904           if (extent<maxLength)
905           {
906             current.insertCharAt(extent, maxLength-extent,gapCharacter);
907             modified = true;
908           }
909         }
910       }
911     }
912     return modified;
913   }
914
915   public HiddenSequences getHiddenSequences()
916   {
917     return hiddenSequences;
918   }
919
920   public CigarArray getCompactAlignment()
921   {
922     SeqCigar alseqs[] = new SeqCigar[sequences.size()];
923     for (int i = 0; i < sequences.size(); i++)
924     {
925       alseqs[i] = new SeqCigar((SequenceI) sequences.elementAt(i));
926     }
927     CigarArray cal = new CigarArray(alseqs);
928     cal.addOperation(CigarArray.M, getWidth());
929     return cal;
930   }
931
932   public void setProperty(Object key, Object value)
933   {
934     if (alignmentProperties == null)
935       alignmentProperties = new Hashtable();
936
937     alignmentProperties.put(key, value);
938   }
939
940   public Object getProperty(Object key)
941   {
942     if (alignmentProperties != null)
943       return alignmentProperties.get(key);
944     else
945       return null;
946   }
947
948   public Hashtable getProperties()
949   {
950     return alignmentProperties;
951   }
952
953   AlignedCodonFrame[] codonFrameList = null;
954
955   /*
956    * (non-Javadoc)
957    * 
958    * @see jalview.datamodel.AlignmentI#addCodonFrame(jalview.datamodel.AlignedCodonFrame)
959    */
960   public void addCodonFrame(AlignedCodonFrame codons)
961   {
962     if (codons == null)
963       return;
964     if (codonFrameList == null)
965     {
966       codonFrameList = new AlignedCodonFrame[]
967       { codons };
968       return;
969     }
970     AlignedCodonFrame[] t = new AlignedCodonFrame[codonFrameList.length + 1];
971     System.arraycopy(codonFrameList, 0, t, 0, codonFrameList.length);
972     t[codonFrameList.length] = codons;
973     codonFrameList = t;
974   }
975
976   /*
977    * (non-Javadoc)
978    * 
979    * @see jalview.datamodel.AlignmentI#getCodonFrame(int)
980    */
981   public AlignedCodonFrame getCodonFrame(int index)
982   {
983     return codonFrameList[index];
984   }
985
986   /*
987    * (non-Javadoc)
988    * 
989    * @see jalview.datamodel.AlignmentI#getCodonFrame(jalview.datamodel.SequenceI)
990    */
991   public AlignedCodonFrame[] getCodonFrame(SequenceI seq)
992   {
993     if (seq == null || codonFrameList == null)
994       return null;
995     Vector cframes = new Vector();
996     for (int f = 0; f < codonFrameList.length; f++)
997     {
998       if (codonFrameList[f].involvesSequence(seq))
999         cframes.addElement(codonFrameList[f]);
1000     }
1001     if (cframes.size() == 0)
1002       return null;
1003     AlignedCodonFrame[] cfr = new AlignedCodonFrame[cframes.size()];
1004     cframes.copyInto(cfr);
1005     return cfr;
1006   }
1007
1008   /*
1009    * (non-Javadoc)
1010    * 
1011    * @see jalview.datamodel.AlignmentI#getCodonFrames()
1012    */
1013   public AlignedCodonFrame[] getCodonFrames()
1014   {
1015     return codonFrameList;
1016   }
1017
1018   /*
1019    * (non-Javadoc)
1020    * 
1021    * @see jalview.datamodel.AlignmentI#removeCodonFrame(jalview.datamodel.AlignedCodonFrame)
1022    */
1023   public boolean removeCodonFrame(AlignedCodonFrame codons)
1024   {
1025     if (codons == null || codonFrameList == null)
1026       return false;
1027     boolean removed = false;
1028     int i = 0, iSize = codonFrameList.length;
1029     while (i < iSize)
1030     {
1031       if (codonFrameList[i] == codons)
1032       {
1033         removed = true;
1034         if (i + 1 < iSize)
1035         {
1036           System.arraycopy(codonFrameList, i + 1, codonFrameList, i, iSize
1037                   - i - 1);
1038         }
1039         iSize--;
1040       }
1041       else
1042       {
1043         i++;
1044       }
1045     }
1046     return removed;
1047   }
1048
1049   public void append(AlignmentI toappend)
1050   {
1051     // TODO test this method for a future 2.5 release
1052     // currently tested for use in jalview.gui.SequenceFetcher
1053     boolean samegap = toappend.getGapCharacter()==getGapCharacter();
1054     char oldc = toappend.getGapCharacter();
1055     boolean hashidden = toappend.getHiddenSequences()!=null && toappend.getHiddenSequences().hiddenSequences!=null;
1056     // get all sequences including any hidden ones
1057     Vector sqs = (hashidden) ? toappend.getHiddenSequences().getFullAlignment().getSequences() : toappend.getSequences();
1058     if (sqs != null)
1059     {
1060       Enumeration sq = sqs.elements();
1061       while (sq.hasMoreElements())
1062       {
1063         SequenceI addedsq=(SequenceI) sq.nextElement();
1064         if (!samegap)
1065         {
1066           char[] oldseq = addedsq.getSequence();
1067           for (int c=0;c<oldseq.length;c++)
1068           {
1069             if (oldseq[c]==oldc)
1070             {
1071               oldseq[c] = gapCharacter;
1072             }
1073           }
1074         }
1075         addSequence(addedsq);
1076       }
1077     }
1078     AlignmentAnnotation[] alan = toappend.getAlignmentAnnotation();
1079     for (int a = 0; alan != null && a < alan.length; a++)
1080     {
1081       addAnnotation(alan[a]);
1082     }
1083     AlignedCodonFrame[] acod = toappend.getCodonFrames();
1084     for (int a = 0; acod != null && a < acod.length; a++)
1085     {
1086       this.addCodonFrame(acod[a]);
1087     }
1088     Vector sg = toappend.getGroups();
1089     if (sg != null)
1090     {
1091       Enumeration el = sg.elements();
1092       while (el.hasMoreElements())
1093       {
1094         addGroup((SequenceGroup) el.nextElement());
1095       }
1096     }
1097     if (toappend.getHiddenSequences()!=null)
1098     {
1099       HiddenSequences hs = toappend.getHiddenSequences();
1100       if (hiddenSequences==null)
1101       {
1102         hiddenSequences = new HiddenSequences(this);
1103       }
1104       if (hs.hiddenSequences!=null)
1105       {
1106         for (int s=0;s<hs.hiddenSequences.length; s++)
1107         {
1108           // hide the newly appended sequence in the alignment
1109           if (hs.hiddenSequences[s]!=null)
1110           {
1111             hiddenSequences.hideSequence(hs.hiddenSequences[s]);
1112           }
1113         }
1114       }
1115     }
1116     if (toappend.getProperties()!=null)
1117     {
1118       // we really can't do very much here - just try to concatenate strings where property collisions occur.
1119       Enumeration key = toappend.getProperties().keys();
1120       while (key.hasMoreElements())
1121       {
1122         Object k = key.nextElement();
1123         Object ourval = this.getProperty(k);
1124         Object toapprop = toappend.getProperty(k);
1125         if (ourval!=null)
1126         {
1127           if (ourval.getClass().equals(toapprop.getClass()) && !ourval.equals(toapprop))
1128           {
1129             if (ourval instanceof String)
1130             {
1131               // append strings
1132               this.setProperty(k, ((String) ourval)+"; "+((String) toapprop));
1133             } else {
1134               if (ourval instanceof Vector)
1135               {
1136                 // append vectors
1137                 Enumeration theirv = ((Vector) toapprop).elements();
1138                 while (theirv.hasMoreElements())
1139                 {
1140                   ((Vector)ourval).addElement(theirv);
1141                 }
1142               }
1143             }
1144           }
1145         } else {
1146           // just add new property directly
1147           setProperty(k, toapprop);
1148         }
1149        
1150       }
1151     }
1152   }
1153
1154 }