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