group/annotation association housekeeping
[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    * remove any annotation that references gp
348    * @param gp (if null, removes all group associated annotation)
349    */
350   private void removeAnnotationForGroup(SequenceGroup gp)
351   {
352     if (annotations==null || annotations.length==0)
353     {
354       return;
355     }
356     // remove annotation very quickly
357     AlignmentAnnotation[] t, todelete = new AlignmentAnnotation[annotations.length], tokeep = new AlignmentAnnotation[annotations.length];
358     int i, p, k;
359     if (gp == null)
360     {
361       for (i = 0, p = 0, k = 0; i < annotations.length; i++)
362       {
363         if (annotations[i].groupRef != null)
364         {
365           todelete[p++] = annotations[i];
366         }
367         else
368         {
369           tokeep[k++] = annotations[i];
370         }
371       }
372     }
373     else
374     {
375       for (i = 0, p = 0, k = 0; i < annotations.length; i++)
376       {
377         if (annotations[i].groupRef == gp)
378         {
379           todelete[p++] = annotations[i];
380         }
381         else
382         {
383           tokeep[k++] = annotations[i];
384         }
385       }
386     }
387     if (p > 0)
388     {
389       // clear out the group associated annotation.
390       for (i = 0; i < p; i++)
391       {
392         unhookAnnotation(todelete[i]);
393         todelete[i] = null;
394       }
395       t = new AlignmentAnnotation[k];
396       for (i = 0; i < k; i++)
397       {
398         t[i] = tokeep[i];
399       }
400       annotations = t;
401     }
402   }
403
404   public void deleteAllGroups()
405   {
406     if (annotations != null)
407     {
408       removeAnnotationForGroup(null);
409     }
410     groups.removeAllElements();
411   }
412
413   /**    */
414   public void deleteGroup(SequenceGroup g)
415   {
416     if (groups.contains(g))
417     {
418       removeAnnotationForGroup(g);
419       groups.removeElement(g);
420     }
421   }
422
423   /**    */
424   public SequenceI findName(String name)
425   {
426     return findName(name, false);
427   }
428
429   /*
430    * (non-Javadoc)
431    * 
432    * @see jalview.datamodel.AlignmentI#findName(java.lang.String, boolean)
433    */
434   public SequenceI findName(String token, boolean b)
435   {
436     return findName(null, token, b);
437   }
438
439   /*
440    * (non-Javadoc)
441    * 
442    * @see jalview.datamodel.AlignmentI#findName(SequenceI, java.lang.String,
443    * boolean)
444    */
445   public SequenceI findName(SequenceI startAfter, String token, boolean b)
446   {
447
448     int i = 0;
449     SequenceI sq = null;
450     String sqname = null;
451     if (startAfter != null)
452     {
453       // try to find the sequence in the alignment
454       boolean matched = false;
455       while (i < sequences.size())
456       {
457         if (getSequenceAt(i++) == startAfter)
458         {
459           matched = true;
460           break;
461         }
462       }
463       if (!matched)
464       {
465         i = 0;
466       }
467     }
468     while (i < sequences.size())
469     {
470       sq = getSequenceAt(i);
471       sqname = sq.getName();
472       if (sqname.equals(token) // exact match
473               || (b && // allow imperfect matches - case varies
474               (sqname.equalsIgnoreCase(token))))
475       {
476         return getSequenceAt(i);
477       }
478
479       i++;
480     }
481
482     return null;
483   }
484
485   public SequenceI[] findSequenceMatch(String name)
486   {
487     Vector matches = new Vector();
488     int i = 0;
489
490     while (i < sequences.size())
491     {
492       if (getSequenceAt(i).getName().equals(name))
493       {
494         matches.addElement(getSequenceAt(i));
495       }
496       i++;
497     }
498
499     SequenceI[] result = new SequenceI[matches.size()];
500     for (i = 0; i < result.length; i++)
501     {
502       result[i] = (SequenceI) matches.elementAt(i);
503     }
504
505     return result;
506
507   }
508
509   /*
510    * (non-Javadoc)
511    * 
512    * @see jalview.datamodel.AlignmentI#findIndex(jalview.datamodel.SequenceI)
513    */
514   public int findIndex(SequenceI s)
515   {
516     int i = 0;
517
518     while (i < sequences.size())
519     {
520       if (s == getSequenceAt(i))
521       {
522         return i;
523       }
524
525       i++;
526     }
527
528     return -1;
529   }
530
531   /*
532    * (non-Javadoc)
533    * 
534    * @see
535    * jalview.datamodel.AlignmentI#findIndex(jalview.datamodel.SearchResults)
536    */
537   public int findIndex(SearchResults results)
538   {
539     int i = 0;
540
541     while (i < sequences.size())
542     {
543       if (results.involvesSequence(getSequenceAt(i)))
544       {
545         return i;
546       }
547       i++;
548     }
549     return -1;
550   }
551
552   /**
553    * DOCUMENT ME!
554    * 
555    * @return DOCUMENT ME!
556    */
557   public int getHeight()
558   {
559     return sequences.size();
560   }
561
562   /**
563    * DOCUMENT ME!
564    * 
565    * @return DOCUMENT ME!
566    */
567   public int getWidth()
568   {
569     int maxLength = -1;
570
571     for (int i = 0; i < sequences.size(); i++)
572     {
573       if (getSequenceAt(i).getLength() > maxLength)
574       {
575         maxLength = getSequenceAt(i).getLength();
576       }
577     }
578
579     return maxLength;
580   }
581
582   /**
583    * DOCUMENT ME!
584    * 
585    * @param gc
586    *          DOCUMENT ME!
587    */
588   public void setGapCharacter(char gc)
589   {
590     gapCharacter = gc;
591
592     for (int i = 0; i < sequences.size(); i++)
593     {
594       Sequence seq = (Sequence) sequences.elementAt(i);
595       seq.setSequence(seq.getSequenceAsString().replace('.', gc).replace(
596               '-', gc).replace(' ', gc));
597     }
598   }
599
600   /**
601    * DOCUMENT ME!
602    * 
603    * @return DOCUMENT ME!
604    */
605   public char getGapCharacter()
606   {
607     return gapCharacter;
608   }
609
610   /**
611    * DOCUMENT ME!
612    * 
613    * @return DOCUMENT ME!
614    */
615   public boolean isAligned()
616   {
617     int width = getWidth();
618
619     for (int i = 0; i < sequences.size(); i++)
620     {
621       if (getSequenceAt(i).getLength() != width)
622       {
623         return false;
624       }
625     }
626
627     return true;
628   }
629
630   /*
631    * (non-Javadoc)
632    * 
633    * @seejalview.datamodel.AlignmentI#deleteAnnotation(jalview.datamodel.
634    * AlignmentAnnotation)
635    */
636   public boolean deleteAnnotation(AlignmentAnnotation aa)
637   {
638     int aSize = 1;
639
640     if (annotations != null)
641     {
642       aSize = annotations.length;
643     }
644
645     if (aSize < 1)
646     {
647       return false;
648     }
649
650     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize - 1];
651
652     boolean swap = false;
653     int tIndex = 0;
654
655     for (int i = 0; i < aSize; i++)
656     {
657       if (annotations[i] == aa)
658       {
659         swap = true;
660         continue;
661       }
662       if (tIndex < temp.length)
663         temp[tIndex++] = annotations[i];
664     }
665
666     if (swap)
667     {
668       annotations = temp;
669       unhookAnnotation(aa);
670     }
671     return swap;
672   }
673
674   /**
675    * remove any object references associated with this annotation
676    * 
677    * @param aa
678    */
679   private void unhookAnnotation(AlignmentAnnotation aa)
680   {
681     if (aa.sequenceRef != null)
682     {
683       aa.sequenceRef.removeAlignmentAnnotation(aa);
684     }
685     if (aa.groupRef != null)
686     {
687       // probably need to do more here in the future (post 2.5.0)
688       aa.groupRef = null;
689     }
690   }
691
692   /*
693    * (non-Javadoc)
694    * 
695    * @seejalview.datamodel.AlignmentI#addAnnotation(jalview.datamodel.
696    * AlignmentAnnotation)
697    */
698   public void addAnnotation(AlignmentAnnotation aa)
699   {
700     addAnnotation(aa, -1);
701   }
702
703   /*
704    * (non-Javadoc)
705    * 
706    * @seejalview.datamodel.AlignmentI#addAnnotation(jalview.datamodel.
707    * AlignmentAnnotation, int)
708    */
709   public void addAnnotation(AlignmentAnnotation aa, int pos)
710   {
711     int aSize = 1;
712     if (annotations != null)
713     {
714       aSize = annotations.length + 1;
715     }
716
717     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize];
718     int i = 0;
719     if (pos == -1 || pos >= aSize)
720     {
721       temp[aSize - 1] = aa;
722     }
723     else
724     {
725       temp[pos] = aa;
726     }
727     if (aSize > 1)
728     {
729       int p = 0;
730       for (i = 0; i < (aSize - 1); i++, p++)
731       {
732         if (p == pos)
733         {
734           p++;
735         }
736         if (p < temp.length)
737         {
738           temp[p] = annotations[i];
739         }
740       }
741     }
742
743     annotations = temp;
744   }
745
746   public void setAnnotationIndex(AlignmentAnnotation aa, int index)
747   {
748     if (aa == null || annotations == null || annotations.length - 1 < index)
749     {
750       return;
751     }
752
753     int aSize = annotations.length;
754     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize];
755
756     temp[index] = aa;
757
758     for (int i = 0; i < aSize; i++)
759     {
760       if (i == index)
761       {
762         continue;
763       }
764
765       if (i < index)
766       {
767         temp[i] = annotations[i];
768       }
769       else
770       {
771         temp[i] = annotations[i - 1];
772       }
773     }
774
775     annotations = temp;
776   }
777
778   /**
779    * DOCUMENT ME!
780    * 
781    * @return DOCUMENT ME!
782    */
783   public AlignmentAnnotation[] getAlignmentAnnotation()
784   {
785     return annotations;
786   }
787
788   public void setNucleotide(boolean b)
789   {
790     if (b)
791     {
792       type = NUCLEOTIDE;
793     }
794     else
795     {
796       type = PROTEIN;
797     }
798   }
799
800   public boolean isNucleotide()
801   {
802     if (type == NUCLEOTIDE)
803     {
804       return true;
805     }
806     else
807     {
808       return false;
809     }
810   }
811
812   public void setDataset(Alignment data)
813   {
814     if (dataset == null && data == null)
815     {
816       // Create a new dataset for this alignment.
817       // Can only be done once, if dataset is not null
818       // This will not be performed
819       SequenceI[] seqs = new SequenceI[getHeight()];
820       SequenceI currentSeq;
821       for (int i = 0; i < getHeight(); i++)
822       {
823         currentSeq = getSequenceAt(i);
824         if (currentSeq.getDatasetSequence() != null)
825         {
826           seqs[i] = (Sequence) currentSeq.getDatasetSequence();
827         }
828         else
829         {
830           seqs[i] = currentSeq.createDatasetSequence();
831         }
832       }
833
834       dataset = new Alignment(seqs);
835     }
836     else if (dataset == null && data != null)
837     {
838       dataset = data;
839     }
840     dataset.addAlignmentRef();
841   }
842
843   /**
844    * reference count for number of alignments referencing this one.
845    */
846   int alignmentRefs = 0;
847
848   /**
849    * increase reference count to this alignment.
850    */
851   private void addAlignmentRef()
852   {
853     alignmentRefs++;
854   }
855
856   public Alignment getDataset()
857   {
858     return dataset;
859   }
860
861   public boolean padGaps()
862   {
863     boolean modified = false;
864
865     // Remove excess gaps from the end of alignment
866     int maxLength = -1;
867
868     SequenceI current;
869     for (int i = 0; i < sequences.size(); i++)
870     {
871       current = getSequenceAt(i);
872       for (int j = current.getLength(); j > maxLength; j--)
873       {
874         if (j > maxLength
875                 && !jalview.util.Comparison.isGap(current.getCharAt(j)))
876         {
877           maxLength = j;
878           break;
879         }
880       }
881     }
882
883     maxLength++;
884
885     int cLength;
886     for (int i = 0; i < sequences.size(); i++)
887     {
888       current = getSequenceAt(i);
889       cLength = current.getLength();
890
891       if (cLength < maxLength)
892       {
893         current.insertCharAt(cLength, maxLength - cLength, gapCharacter);
894         modified = true;
895       }
896       else if (current.getLength() > maxLength)
897       {
898         current.deleteChars(maxLength, current.getLength());
899       }
900     }
901     return modified;
902   }
903
904   /**
905    * Justify the sequences to the left or right by deleting and inserting gaps
906    * before the initial residue or after the terminal residue
907    * 
908    * @param right
909    *          true if alignment padded to right, false to justify to left
910    * @return true if alignment was changed
911    */
912   public boolean justify(boolean right)
913   {
914     boolean modified = false;
915
916     // Remove excess gaps from the end of alignment
917     int maxLength = -1;
918     int ends[] = new int[sequences.size() * 2];
919     SequenceI current;
920     for (int i = 0; i < sequences.size(); i++)
921     {
922       current = getSequenceAt(i);
923       // This should really be a sequence method
924       ends[i * 2] = current.findIndex(current.getStart());
925       ends[i * 2 + 1] = current.findIndex(current.getStart()
926               + current.getLength());
927       boolean hitres = false;
928       for (int j = 0, rs = 0, ssiz = current.getLength(); j < ssiz; j++)
929       {
930         if (!jalview.util.Comparison.isGap(current.getCharAt(j)))
931         {
932           if (!hitres)
933           {
934             ends[i * 2] = j;
935             hitres = true;
936           }
937           else
938           {
939             ends[i * 2 + 1] = j;
940             if (j - ends[i * 2] > maxLength)
941             {
942               maxLength = j - ends[i * 2];
943             }
944           }
945         }
946       }
947     }
948
949     maxLength++;
950     // now edit the flanking gaps to justify to either left or right
951     int cLength, extent, diff;
952     for (int i = 0; i < sequences.size(); i++)
953     {
954       current = getSequenceAt(i);
955
956       cLength = 1 + ends[i * 2 + 1] - ends[i * 2];
957       diff = maxLength - cLength; // number of gaps to indent
958       extent = current.getLength();
959       if (right)
960       {
961         // right justify
962         if (extent > ends[i * 2 + 1])
963         {
964           current.deleteChars(ends[i * 2 + 1] + 1, extent);
965           modified = true;
966         }
967         if (ends[i * 2] > diff)
968         {
969           current.deleteChars(0, ends[i * 2] - diff);
970           modified = true;
971         }
972         else
973         {
974           if (ends[i * 2] < diff)
975           {
976             current.insertCharAt(0, diff - ends[i * 2], gapCharacter);
977             modified = true;
978           }
979         }
980       }
981       else
982       {
983         // left justify
984         if (ends[i * 2] > 0)
985         {
986           current.deleteChars(0, ends[i * 2]);
987           modified = true;
988           ends[i * 2 + 1] -= ends[i * 2];
989           extent -= ends[i * 2];
990         }
991         if (extent > maxLength)
992         {
993           current.deleteChars(maxLength + 1, extent);
994           modified = true;
995         }
996         else
997         {
998           if (extent < maxLength)
999           {
1000             current.insertCharAt(extent, maxLength - extent, gapCharacter);
1001             modified = true;
1002           }
1003         }
1004       }
1005     }
1006     return modified;
1007   }
1008
1009   public HiddenSequences getHiddenSequences()
1010   {
1011     return hiddenSequences;
1012   }
1013
1014   public CigarArray getCompactAlignment()
1015   {
1016     SeqCigar alseqs[] = new SeqCigar[sequences.size()];
1017     for (int i = 0; i < sequences.size(); i++)
1018     {
1019       alseqs[i] = new SeqCigar((SequenceI) sequences.elementAt(i));
1020     }
1021     CigarArray cal = new CigarArray(alseqs);
1022     cal.addOperation(CigarArray.M, getWidth());
1023     return cal;
1024   }
1025
1026   public void setProperty(Object key, Object value)
1027   {
1028     if (alignmentProperties == null)
1029       alignmentProperties = new Hashtable();
1030
1031     alignmentProperties.put(key, value);
1032   }
1033
1034   public Object getProperty(Object key)
1035   {
1036     if (alignmentProperties != null)
1037       return alignmentProperties.get(key);
1038     else
1039       return null;
1040   }
1041
1042   public Hashtable getProperties()
1043   {
1044     return alignmentProperties;
1045   }
1046
1047   AlignedCodonFrame[] codonFrameList = null;
1048
1049   /*
1050    * (non-Javadoc)
1051    * 
1052    * @see
1053    * jalview.datamodel.AlignmentI#addCodonFrame(jalview.datamodel.AlignedCodonFrame
1054    * )
1055    */
1056   public void addCodonFrame(AlignedCodonFrame codons)
1057   {
1058     if (codons == null)
1059       return;
1060     if (codonFrameList == null)
1061     {
1062       codonFrameList = new AlignedCodonFrame[]
1063       { codons };
1064       return;
1065     }
1066     AlignedCodonFrame[] t = new AlignedCodonFrame[codonFrameList.length + 1];
1067     System.arraycopy(codonFrameList, 0, t, 0, codonFrameList.length);
1068     t[codonFrameList.length] = codons;
1069     codonFrameList = t;
1070   }
1071
1072   /*
1073    * (non-Javadoc)
1074    * 
1075    * @see jalview.datamodel.AlignmentI#getCodonFrame(int)
1076    */
1077   public AlignedCodonFrame getCodonFrame(int index)
1078   {
1079     return codonFrameList[index];
1080   }
1081
1082   /*
1083    * (non-Javadoc)
1084    * 
1085    * @see
1086    * jalview.datamodel.AlignmentI#getCodonFrame(jalview.datamodel.SequenceI)
1087    */
1088   public AlignedCodonFrame[] getCodonFrame(SequenceI seq)
1089   {
1090     if (seq == null || codonFrameList == null)
1091       return null;
1092     Vector cframes = new Vector();
1093     for (int f = 0; f < codonFrameList.length; f++)
1094     {
1095       if (codonFrameList[f].involvesSequence(seq))
1096         cframes.addElement(codonFrameList[f]);
1097     }
1098     if (cframes.size() == 0)
1099       return null;
1100     AlignedCodonFrame[] cfr = new AlignedCodonFrame[cframes.size()];
1101     cframes.copyInto(cfr);
1102     return cfr;
1103   }
1104
1105   /*
1106    * (non-Javadoc)
1107    * 
1108    * @see jalview.datamodel.AlignmentI#getCodonFrames()
1109    */
1110   public AlignedCodonFrame[] getCodonFrames()
1111   {
1112     return codonFrameList;
1113   }
1114
1115   /*
1116    * (non-Javadoc)
1117    * 
1118    * @seejalview.datamodel.AlignmentI#removeCodonFrame(jalview.datamodel.
1119    * AlignedCodonFrame)
1120    */
1121   public boolean removeCodonFrame(AlignedCodonFrame codons)
1122   {
1123     if (codons == null || codonFrameList == null)
1124       return false;
1125     boolean removed = false;
1126     int i = 0, iSize = codonFrameList.length;
1127     while (i < iSize)
1128     {
1129       if (codonFrameList[i] == codons)
1130       {
1131         removed = true;
1132         if (i + 1 < iSize)
1133         {
1134           System.arraycopy(codonFrameList, i + 1, codonFrameList, i, iSize
1135                   - i - 1);
1136         }
1137         iSize--;
1138       }
1139       else
1140       {
1141         i++;
1142       }
1143     }
1144     return removed;
1145   }
1146
1147   public void append(AlignmentI toappend)
1148   {
1149     // TODO test this method for a future 2.5 release
1150     // currently tested for use in jalview.gui.SequenceFetcher
1151     boolean samegap = toappend.getGapCharacter() == getGapCharacter();
1152     char oldc = toappend.getGapCharacter();
1153     boolean hashidden = toappend.getHiddenSequences() != null
1154             && toappend.getHiddenSequences().hiddenSequences != null;
1155     // get all sequences including any hidden ones
1156     Vector sqs = (hashidden) ? toappend.getHiddenSequences()
1157             .getFullAlignment().getSequences() : toappend.getSequences();
1158     if (sqs != null)
1159     {
1160       Enumeration sq = sqs.elements();
1161       while (sq.hasMoreElements())
1162       {
1163         SequenceI addedsq = (SequenceI) sq.nextElement();
1164         if (!samegap)
1165         {
1166           char[] oldseq = addedsq.getSequence();
1167           for (int c = 0; c < oldseq.length; c++)
1168           {
1169             if (oldseq[c] == oldc)
1170             {
1171               oldseq[c] = gapCharacter;
1172             }
1173           }
1174         }
1175         addSequence(addedsq);
1176       }
1177     }
1178     AlignmentAnnotation[] alan = toappend.getAlignmentAnnotation();
1179     for (int a = 0; alan != null && a < alan.length; a++)
1180     {
1181       addAnnotation(alan[a]);
1182     }
1183     AlignedCodonFrame[] acod = toappend.getCodonFrames();
1184     for (int a = 0; acod != null && a < acod.length; a++)
1185     {
1186       this.addCodonFrame(acod[a]);
1187     }
1188     Vector sg = toappend.getGroups();
1189     if (sg != null)
1190     {
1191       Enumeration el = sg.elements();
1192       while (el.hasMoreElements())
1193       {
1194         addGroup((SequenceGroup) el.nextElement());
1195       }
1196     }
1197     if (toappend.getHiddenSequences() != null)
1198     {
1199       HiddenSequences hs = toappend.getHiddenSequences();
1200       if (hiddenSequences == null)
1201       {
1202         hiddenSequences = new HiddenSequences(this);
1203       }
1204       if (hs.hiddenSequences != null)
1205       {
1206         for (int s = 0; s < hs.hiddenSequences.length; s++)
1207         {
1208           // hide the newly appended sequence in the alignment
1209           if (hs.hiddenSequences[s] != null)
1210           {
1211             hiddenSequences.hideSequence(hs.hiddenSequences[s]);
1212           }
1213         }
1214       }
1215     }
1216     if (toappend.getProperties() != null)
1217     {
1218       // we really can't do very much here - just try to concatenate strings
1219       // where property collisions occur.
1220       Enumeration key = toappend.getProperties().keys();
1221       while (key.hasMoreElements())
1222       {
1223         Object k = key.nextElement();
1224         Object ourval = this.getProperty(k);
1225         Object toapprop = toappend.getProperty(k);
1226         if (ourval != null)
1227         {
1228           if (ourval.getClass().equals(toapprop.getClass())
1229                   && !ourval.equals(toapprop))
1230           {
1231             if (ourval instanceof String)
1232             {
1233               // append strings
1234               this.setProperty(k, ((String) ourval) + "; "
1235                       + ((String) toapprop));
1236             }
1237             else
1238             {
1239               if (ourval instanceof Vector)
1240               {
1241                 // append vectors
1242                 Enumeration theirv = ((Vector) toapprop).elements();
1243                 while (theirv.hasMoreElements())
1244                 {
1245                   ((Vector) ourval).addElement(theirv);
1246                 }
1247               }
1248             }
1249           }
1250         }
1251         else
1252         {
1253           // just add new property directly
1254           setProperty(k, toapprop);
1255         }
1256
1257       }
1258     }
1259   }
1260
1261 }