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