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