JAL-2110 javadoc and TODO re shortcomings of implementation
[jalview.git] / src / jalview / datamodel / Alignment.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
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
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.datamodel;
22
23 import jalview.analysis.AlignmentUtils;
24 import jalview.io.FastaFile;
25 import jalview.util.Comparison;
26 import jalview.util.MessageManager;
27
28 import java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.Enumeration;
31 import java.util.HashSet;
32 import java.util.Hashtable;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Set;
36 import java.util.Vector;
37
38 /**
39  * Data structure to hold and manipulate a multiple sequence alignment
40  */
41 /**
42  * @author JimP
43  * 
44  */
45 public class Alignment implements AlignmentI
46 {
47   private Alignment dataset;
48
49   protected List<SequenceI> sequences;
50
51   protected List<SequenceGroup> groups;
52
53   protected char gapCharacter = '-';
54
55   protected int type = NUCLEOTIDE;
56
57   public static final int PROTEIN = 0;
58
59   public static final int NUCLEOTIDE = 1;
60
61   public boolean hasRNAStructure = false;
62
63   public AlignmentAnnotation[] annotations;
64
65   HiddenSequences hiddenSequences;
66
67   public Hashtable alignmentProperties;
68
69   private List<AlignedCodonFrame> codonFrameList;
70
71   private void initAlignment(SequenceI[] seqs)
72   {
73     groups = Collections.synchronizedList(new ArrayList<SequenceGroup>());
74     hiddenSequences = new HiddenSequences(this);
75     codonFrameList = new ArrayList<AlignedCodonFrame>();
76
77     if (Comparison.isNucleotide(seqs))
78     {
79       type = NUCLEOTIDE;
80     }
81     else
82     {
83       type = PROTEIN;
84     }
85
86     sequences = Collections.synchronizedList(new ArrayList<SequenceI>());
87
88     for (int i = 0; i < seqs.length; i++)
89     {
90       sequences.add(seqs[i]);
91     }
92
93   }
94
95   /**
96    * Make a 'copy' alignment - sequences have new copies of features and
97    * annotations, but share the original dataset sequences.
98    */
99   public Alignment(AlignmentI al)
100   {
101     SequenceI[] seqs = al.getSequencesArray();
102     for (int i = 0; i < seqs.length; i++)
103     {
104       seqs[i] = new Sequence(seqs[i]);
105     }
106
107     initAlignment(seqs);
108
109     /*
110      * Share the same dataset sequence mappings (if any). 
111      */
112     if (dataset == null && al.getDataset() == null)
113     {
114       this.setCodonFrames(al.getCodonFrames());
115     }
116   }
117
118   /**
119    * Make an alignment from an array of Sequences.
120    * 
121    * @param sequences
122    */
123   public Alignment(SequenceI[] seqs)
124   {
125     initAlignment(seqs);
126   }
127
128   /**
129    * Make a new alignment from an array of SeqCigars
130    * 
131    * @param seqs
132    *          SeqCigar[]
133    */
134   public Alignment(SeqCigar[] alseqs)
135   {
136     SequenceI[] seqs = SeqCigar.createAlignmentSequences(alseqs,
137             gapCharacter, new ColumnSelection(), null);
138     initAlignment(seqs);
139   }
140
141   /**
142    * Make a new alignment from an CigarArray JBPNote - can only do this when
143    * compactAlignment does not contain hidden regions. JBPNote - must also check
144    * that compactAlignment resolves to a set of SeqCigars - or construct them
145    * appropriately.
146    * 
147    * @param compactAlignment
148    *          CigarArray
149    */
150   public static AlignmentI createAlignment(CigarArray compactAlignment)
151   {
152     throw new Error(
153             MessageManager
154                     .getString("error.alignment_cigararray_not_implemented"));
155     // this(compactAlignment.refCigars);
156   }
157
158   @Override
159   public List<SequenceI> getSequences()
160   {
161     return sequences;
162   }
163
164   @Override
165   public List<SequenceI> getSequences(
166           Map<SequenceI, SequenceCollectionI> hiddenReps)
167   {
168     // TODO: in jalview 2.8 we don't do anything with hiddenreps - fix design to
169     // work on this.
170     return sequences;
171   }
172
173   @Override
174   public SequenceI[] getSequencesArray()
175   {
176     if (sequences == null)
177     {
178       return null;
179     }
180     synchronized (sequences)
181     {
182       return sequences.toArray(new SequenceI[sequences.size()]);
183     }
184   }
185
186   /**
187    * Returns a map of lists of sequences keyed by sequence name.
188    * 
189    * @return
190    */
191   @Override
192   public Map<String, List<SequenceI>> getSequencesByName()
193   {
194     return AlignmentUtils.getSequencesByName(this);
195   }
196
197   /**
198    * DOCUMENT ME!
199    * 
200    * @param i
201    *          DOCUMENT ME!
202    * 
203    * @return DOCUMENT ME!
204    */
205   @Override
206   public SequenceI getSequenceAt(int i)
207   {
208     synchronized (sequences)
209     {
210       if (i > -1 && i < sequences.size())
211       {
212         return sequences.get(i);
213       }
214     }
215     return null;
216   }
217
218   /**
219    * Adds a sequence to the alignment. Recalculates maxLength and size.
220    * 
221    * @param snew
222    */
223   @Override
224   public void addSequence(SequenceI snew)
225   {
226     if (dataset != null)
227     {
228       // maintain dataset integrity
229       if (snew.getDatasetSequence() != null)
230       {
231         getDataset().addSequence(snew.getDatasetSequence());
232       }
233       else
234       {
235         // derive new sequence
236         SequenceI adding = snew.deriveSequence();
237         getDataset().addSequence(adding.getDatasetSequence());
238         snew = adding;
239       }
240     }
241     if (sequences == null)
242     {
243       initAlignment(new SequenceI[] { snew });
244     }
245     else
246     {
247       synchronized (sequences)
248       {
249         sequences.add(snew);
250       }
251     }
252     if (hiddenSequences != null)
253     {
254       hiddenSequences.adjustHeightSequenceAdded();
255     }
256   }
257
258   /**
259    * Adds a sequence to the alignment. Recalculates maxLength and size.
260    * 
261    * @param snew
262    */
263   @Override
264   public void setSequenceAt(int i, SequenceI snew)
265   {
266     synchronized (sequences)
267     {
268       deleteSequence(i);
269       sequences.set(i, snew);
270     }
271   }
272
273   /**
274    * DOCUMENT ME!
275    * 
276    * @return DOCUMENT ME!
277    */
278   @Override
279   public List<SequenceGroup> getGroups()
280   {
281     return groups;
282   }
283
284   @Override
285   public void finalize()
286   {
287     if (getDataset() != null)
288     {
289       getDataset().removeAlignmentRef();
290     }
291
292     dataset = null;
293     sequences = null;
294     groups = null;
295     annotations = null;
296     hiddenSequences = null;
297   }
298
299   /**
300    * decrement the alignmentRefs counter by one and call finalize if it goes to
301    * zero.
302    */
303   private void removeAlignmentRef()
304   {
305     if (--alignmentRefs == 0)
306     {
307       finalize();
308     }
309   }
310
311   /**
312    * DOCUMENT ME!
313    * 
314    * @param s
315    *          DOCUMENT ME!
316    */
317   @Override
318   public void deleteSequence(SequenceI s)
319   {
320     deleteSequence(findIndex(s));
321   }
322
323   /**
324    * DOCUMENT ME!
325    * 
326    * @param i
327    *          DOCUMENT ME!
328    */
329   @Override
330   public void deleteSequence(int i)
331   {
332     if (i > -1 && i < getHeight())
333     {
334       synchronized (sequences)
335       {
336         sequences.remove(i);
337         hiddenSequences.adjustHeightSequenceDeleted(i);
338       }
339     }
340   }
341
342   /*
343    * (non-Javadoc)
344    * 
345    * @see jalview.datamodel.AlignmentI#findGroup(jalview.datamodel.SequenceI)
346    */
347   @Override
348   public SequenceGroup findGroup(SequenceI s)
349   {
350     synchronized (groups)
351     {
352       for (int i = 0; i < this.groups.size(); i++)
353       {
354         SequenceGroup sg = groups.get(i);
355
356         if (sg.getSequences(null).contains(s))
357         {
358           return sg;
359         }
360       }
361     }
362     return null;
363   }
364
365   /*
366    * (non-Javadoc)
367    * 
368    * @see
369    * jalview.datamodel.AlignmentI#findAllGroups(jalview.datamodel.SequenceI)
370    */
371   @Override
372   public SequenceGroup[] findAllGroups(SequenceI s)
373   {
374     ArrayList<SequenceGroup> temp = new ArrayList<SequenceGroup>();
375
376     synchronized (groups)
377     {
378       int gSize = groups.size();
379       for (int i = 0; i < gSize; i++)
380       {
381         SequenceGroup sg = groups.get(i);
382         if (sg == null || sg.getSequences() == null)
383         {
384           this.deleteGroup(sg);
385           gSize--;
386           continue;
387         }
388
389         if (sg.getSequences().contains(s))
390         {
391           temp.add(sg);
392         }
393       }
394     }
395     SequenceGroup[] ret = new SequenceGroup[temp.size()];
396     return temp.toArray(ret);
397   }
398
399   /**    */
400   @Override
401   public void addGroup(SequenceGroup sg)
402   {
403     synchronized (groups)
404     {
405       if (!groups.contains(sg))
406       {
407         if (hiddenSequences.getSize() > 0)
408         {
409           int i, iSize = sg.getSize();
410           for (i = 0; i < iSize; i++)
411           {
412             if (!sequences.contains(sg.getSequenceAt(i)))
413             {
414               sg.deleteSequence(sg.getSequenceAt(i), false);
415               iSize--;
416               i--;
417             }
418           }
419
420           if (sg.getSize() < 1)
421           {
422             return;
423           }
424         }
425         sg.setContext(this);
426         groups.add(sg);
427       }
428     }
429   }
430
431   /**
432    * remove any annotation that references gp
433    * 
434    * @param gp
435    *          (if null, removes all group associated annotation)
436    */
437   private void removeAnnotationForGroup(SequenceGroup gp)
438   {
439     if (annotations == null || annotations.length == 0)
440     {
441       return;
442     }
443     // remove annotation very quickly
444     AlignmentAnnotation[] t, todelete = new AlignmentAnnotation[annotations.length], tokeep = new AlignmentAnnotation[annotations.length];
445     int i, p, k;
446     if (gp == null)
447     {
448       for (i = 0, p = 0, k = 0; i < annotations.length; i++)
449       {
450         if (annotations[i].groupRef != null)
451         {
452           todelete[p++] = annotations[i];
453         }
454         else
455         {
456           tokeep[k++] = annotations[i];
457         }
458       }
459     }
460     else
461     {
462       for (i = 0, p = 0, k = 0; i < annotations.length; i++)
463       {
464         if (annotations[i].groupRef == gp)
465         {
466           todelete[p++] = annotations[i];
467         }
468         else
469         {
470           tokeep[k++] = annotations[i];
471         }
472       }
473     }
474     if (p > 0)
475     {
476       // clear out the group associated annotation.
477       for (i = 0; i < p; i++)
478       {
479         unhookAnnotation(todelete[i]);
480         todelete[i] = null;
481       }
482       t = new AlignmentAnnotation[k];
483       for (i = 0; i < k; i++)
484       {
485         t[i] = tokeep[i];
486       }
487       annotations = t;
488     }
489   }
490
491   @Override
492   public void deleteAllGroups()
493   {
494     synchronized (groups)
495     {
496       if (annotations != null)
497       {
498         removeAnnotationForGroup(null);
499       }
500       for (SequenceGroup sg : groups)
501       {
502         sg.setContext(null);
503       }
504       groups.clear();
505     }
506   }
507
508   /**    */
509   @Override
510   public void deleteGroup(SequenceGroup g)
511   {
512     synchronized (groups)
513     {
514       if (groups.contains(g))
515       {
516         removeAnnotationForGroup(g);
517         groups.remove(g);
518         g.setContext(null);
519       }
520     }
521   }
522
523   /**    */
524   @Override
525   public SequenceI findName(String name)
526   {
527     return findName(name, false);
528   }
529
530   /*
531    * (non-Javadoc)
532    * 
533    * @see jalview.datamodel.AlignmentI#findName(java.lang.String, boolean)
534    */
535   @Override
536   public SequenceI findName(String token, boolean b)
537   {
538     return findName(null, token, b);
539   }
540
541   /*
542    * (non-Javadoc)
543    * 
544    * @see jalview.datamodel.AlignmentI#findName(SequenceI, java.lang.String,
545    * boolean)
546    */
547   @Override
548   public SequenceI findName(SequenceI startAfter, String token, boolean b)
549   {
550
551     int i = 0;
552     SequenceI sq = null;
553     String sqname = null;
554     if (startAfter != null)
555     {
556       // try to find the sequence in the alignment
557       boolean matched = false;
558       while (i < sequences.size())
559       {
560         if (getSequenceAt(i++) == startAfter)
561         {
562           matched = true;
563           break;
564         }
565       }
566       if (!matched)
567       {
568         i = 0;
569       }
570     }
571     while (i < sequences.size())
572     {
573       sq = getSequenceAt(i);
574       sqname = sq.getName();
575       if (sqname.equals(token) // exact match
576               || (b && // allow imperfect matches - case varies
577               (sqname.equalsIgnoreCase(token))))
578       {
579         return getSequenceAt(i);
580       }
581
582       i++;
583     }
584
585     return null;
586   }
587
588   @Override
589   public SequenceI[] findSequenceMatch(String name)
590   {
591     Vector matches = new Vector();
592     int i = 0;
593
594     while (i < sequences.size())
595     {
596       if (getSequenceAt(i).getName().equals(name))
597       {
598         matches.addElement(getSequenceAt(i));
599       }
600       i++;
601     }
602
603     SequenceI[] result = new SequenceI[matches.size()];
604     for (i = 0; i < result.length; i++)
605     {
606       result[i] = (SequenceI) matches.elementAt(i);
607     }
608
609     return result;
610
611   }
612
613   /*
614    * (non-Javadoc)
615    * 
616    * @see jalview.datamodel.AlignmentI#findIndex(jalview.datamodel.SequenceI)
617    */
618   @Override
619   public int findIndex(SequenceI s)
620   {
621     int i = 0;
622
623     while (i < sequences.size())
624     {
625       if (s == getSequenceAt(i))
626       {
627         return i;
628       }
629
630       i++;
631     }
632
633     return -1;
634   }
635
636   /*
637    * (non-Javadoc)
638    * 
639    * @see
640    * jalview.datamodel.AlignmentI#findIndex(jalview.datamodel.SearchResults)
641    */
642   @Override
643   public int findIndex(SearchResults results)
644   {
645     int i = 0;
646
647     while (i < sequences.size())
648     {
649       if (results.involvesSequence(getSequenceAt(i)))
650       {
651         return i;
652       }
653       i++;
654     }
655     return -1;
656   }
657
658   /**
659    * DOCUMENT ME!
660    * 
661    * @return DOCUMENT ME!
662    */
663   @Override
664   public int getHeight()
665   {
666     return sequences.size();
667   }
668
669   /**
670    * DOCUMENT ME!
671    * 
672    * @return DOCUMENT ME!
673    */
674   @Override
675   public int getWidth()
676   {
677     int maxLength = -1;
678
679     for (int i = 0; i < sequences.size(); i++)
680     {
681       if (getSequenceAt(i).getLength() > maxLength)
682       {
683         maxLength = getSequenceAt(i).getLength();
684       }
685     }
686
687     return maxLength;
688   }
689
690   /**
691    * DOCUMENT ME!
692    * 
693    * @param gc
694    *          DOCUMENT ME!
695    */
696   @Override
697   public void setGapCharacter(char gc)
698   {
699     gapCharacter = gc;
700     synchronized (sequences)
701     {
702       for (SequenceI seq : sequences)
703       {
704         seq.setSequence(seq.getSequenceAsString().replace('.', gc)
705                 .replace('-', gc).replace(' ', gc));
706       }
707     }
708   }
709
710   /**
711    * DOCUMENT ME!
712    * 
713    * @return DOCUMENT ME!
714    */
715   @Override
716   public char getGapCharacter()
717   {
718     return gapCharacter;
719   }
720
721   /*
722    * (non-Javadoc)
723    * 
724    * @see jalview.datamodel.AlignmentI#isAligned()
725    */
726   @Override
727   public boolean isAligned()
728   {
729     return isAligned(false);
730   }
731
732   /*
733    * (non-Javadoc)
734    * 
735    * @see jalview.datamodel.AlignmentI#isAligned(boolean)
736    */
737   @Override
738   public boolean isAligned(boolean includeHidden)
739   {
740     int width = getWidth();
741     if (hiddenSequences == null || hiddenSequences.getSize() == 0)
742     {
743       includeHidden = true; // no hidden sequences to check against.
744     }
745     for (int i = 0; i < sequences.size(); i++)
746     {
747       if (includeHidden || !hiddenSequences.isHidden(getSequenceAt(i)))
748       {
749         if (getSequenceAt(i).getLength() != width)
750         {
751           return false;
752         }
753       }
754     }
755
756     return true;
757   }
758
759   /**
760    * Delete all annotations, including auto-calculated if the flag is set true.
761    * Returns true if at least one annotation was deleted, else false.
762    * 
763    * @param includingAutoCalculated
764    * @return
765    */
766   @Override
767   public boolean deleteAllAnnotations(boolean includingAutoCalculated)
768   {
769     boolean result = false;
770     for (AlignmentAnnotation alan : getAlignmentAnnotation())
771     {
772       if (!alan.autoCalculated || includingAutoCalculated)
773       {
774         deleteAnnotation(alan);
775         result = true;
776       }
777     }
778     return result;
779   }
780
781   /*
782    * (non-Javadoc)
783    * 
784    * @seejalview.datamodel.AlignmentI#deleteAnnotation(jalview.datamodel.
785    * AlignmentAnnotation)
786    */
787   @Override
788   public boolean deleteAnnotation(AlignmentAnnotation aa)
789   {
790     return deleteAnnotation(aa, true);
791   }
792
793   @Override
794   public boolean deleteAnnotation(AlignmentAnnotation aa, boolean unhook)
795   {
796     int aSize = 1;
797
798     if (annotations != null)
799     {
800       aSize = annotations.length;
801     }
802
803     if (aSize < 1)
804     {
805       return false;
806     }
807
808     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize - 1];
809
810     boolean swap = false;
811     int tIndex = 0;
812
813     for (int i = 0; i < aSize; i++)
814     {
815       if (annotations[i] == aa)
816       {
817         swap = true;
818         continue;
819       }
820       if (tIndex < temp.length)
821       {
822         temp[tIndex++] = annotations[i];
823       }
824     }
825
826     if (swap)
827     {
828       annotations = temp;
829       if (unhook)
830       {
831         unhookAnnotation(aa);
832       }
833     }
834     return swap;
835   }
836
837   /**
838    * remove any object references associated with this annotation
839    * 
840    * @param aa
841    */
842   private void unhookAnnotation(AlignmentAnnotation aa)
843   {
844     if (aa.sequenceRef != null)
845     {
846       aa.sequenceRef.removeAlignmentAnnotation(aa);
847     }
848     if (aa.groupRef != null)
849     {
850       // probably need to do more here in the future (post 2.5.0)
851       aa.groupRef = null;
852     }
853   }
854
855   /*
856    * (non-Javadoc)
857    * 
858    * @seejalview.datamodel.AlignmentI#addAnnotation(jalview.datamodel.
859    * AlignmentAnnotation)
860    */
861   @Override
862   public void addAnnotation(AlignmentAnnotation aa)
863   {
864     addAnnotation(aa, -1);
865   }
866
867   /*
868    * (non-Javadoc)
869    * 
870    * @seejalview.datamodel.AlignmentI#addAnnotation(jalview.datamodel.
871    * AlignmentAnnotation, int)
872    */
873   @Override
874   public void addAnnotation(AlignmentAnnotation aa, int pos)
875   {
876     if (aa.getRNAStruc() != null)
877     {
878       hasRNAStructure = true;
879     }
880
881     int aSize = 1;
882     if (annotations != null)
883     {
884       aSize = annotations.length + 1;
885     }
886
887     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize];
888     int i = 0;
889     if (pos == -1 || pos >= aSize)
890     {
891       temp[aSize - 1] = aa;
892     }
893     else
894     {
895       temp[pos] = aa;
896     }
897     if (aSize > 1)
898     {
899       int p = 0;
900       for (i = 0; i < (aSize - 1); i++, p++)
901       {
902         if (p == pos)
903         {
904           p++;
905         }
906         if (p < temp.length)
907         {
908           temp[p] = annotations[i];
909         }
910       }
911     }
912
913     annotations = temp;
914   }
915
916   @Override
917   public void setAnnotationIndex(AlignmentAnnotation aa, int index)
918   {
919     if (aa == null || annotations == null || annotations.length - 1 < index)
920     {
921       return;
922     }
923
924     int aSize = annotations.length;
925     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize];
926
927     temp[index] = aa;
928
929     for (int i = 0; i < aSize; i++)
930     {
931       if (i == index)
932       {
933         continue;
934       }
935
936       if (i < index)
937       {
938         temp[i] = annotations[i];
939       }
940       else
941       {
942         temp[i] = annotations[i - 1];
943       }
944     }
945
946     annotations = temp;
947   }
948
949   @Override
950   /**
951    * returns all annotation on the alignment
952    */
953   public AlignmentAnnotation[] getAlignmentAnnotation()
954   {
955     return annotations;
956   }
957
958   @Override
959   public void setNucleotide(boolean b)
960   {
961     if (b)
962     {
963       type = NUCLEOTIDE;
964     }
965     else
966     {
967       type = PROTEIN;
968     }
969   }
970
971   @Override
972   public boolean isNucleotide()
973   {
974     if (type == NUCLEOTIDE)
975     {
976       return true;
977     }
978     else
979     {
980       return false;
981     }
982   }
983
984   @Override
985   public boolean hasRNAStructure()
986   {
987     // TODO can it happen that structure is removed from alignment?
988     return hasRNAStructure;
989   }
990
991   @Override
992   public void setDataset(AlignmentI data)
993   {
994     if (dataset == null && data == null)
995     {
996       createDatasetAlignment();
997     }
998     else if (dataset == null && data != null)
999     {
1000       if (!(data instanceof Alignment))
1001       {
1002         throw new Error(
1003                 "Implementation Error: jalview.datamodel.Alignment does not yet support other implementations of AlignmentI as its dataset reference");
1004       }
1005       dataset = (Alignment) data;
1006       for (int i = 0; i < getHeight(); i++)
1007       {
1008         SequenceI currentSeq = getSequenceAt(i);
1009         SequenceI dsq = currentSeq.getDatasetSequence();
1010         if (dsq == null)
1011         {
1012           dsq = currentSeq.createDatasetSequence();
1013           dataset.addSequence(dsq);
1014         }
1015         else
1016         {
1017           while (dsq.getDatasetSequence() != null)
1018           {
1019             dsq = dsq.getDatasetSequence();
1020           }
1021           if (dataset.findIndex(dsq) == -1)
1022           {
1023             dataset.addSequence(dsq);
1024           }
1025         }
1026       }
1027     }
1028     dataset.addAlignmentRef();
1029   }
1030
1031   /**
1032    * Creates a new dataset for this alignment. Can only be done once - if
1033    * dataset is not null this will not be performed.
1034    */
1035   public void createDatasetAlignment()
1036   {
1037     if (dataset != null)
1038     {
1039       return;
1040     }
1041     SequenceI[] seqs = new SequenceI[getHeight()];
1042     SequenceI currentSeq;
1043     for (int i = 0; i < getHeight(); i++)
1044     {
1045       currentSeq = getSequenceAt(i);
1046       if (currentSeq.getDatasetSequence() != null)
1047       {
1048         seqs[i] = currentSeq.getDatasetSequence();
1049       }
1050       else
1051       {
1052         seqs[i] = currentSeq.createDatasetSequence();
1053       }
1054     }
1055
1056     dataset = new Alignment(seqs);
1057     // move mappings to the dataset alignment
1058     dataset.codonFrameList = this.codonFrameList;
1059     this.codonFrameList = null;
1060   }
1061
1062   /**
1063    * reference count for number of alignments referencing this one.
1064    */
1065   int alignmentRefs = 0;
1066
1067   /**
1068    * increase reference count to this alignment.
1069    */
1070   private void addAlignmentRef()
1071   {
1072     alignmentRefs++;
1073   }
1074
1075   @Override
1076   public Alignment getDataset()
1077   {
1078     return dataset;
1079   }
1080
1081   @Override
1082   public boolean padGaps()
1083   {
1084     boolean modified = false;
1085
1086     // Remove excess gaps from the end of alignment
1087     int maxLength = -1;
1088
1089     SequenceI current;
1090     for (int i = 0; i < sequences.size(); i++)
1091     {
1092       current = getSequenceAt(i);
1093       for (int j = current.getLength(); j > maxLength; j--)
1094       {
1095         if (j > maxLength
1096                 && !jalview.util.Comparison.isGap(current.getCharAt(j)))
1097         {
1098           maxLength = j;
1099           break;
1100         }
1101       }
1102     }
1103
1104     maxLength++;
1105
1106     int cLength;
1107     for (int i = 0; i < sequences.size(); i++)
1108     {
1109       current = getSequenceAt(i);
1110       cLength = current.getLength();
1111
1112       if (cLength < maxLength)
1113       {
1114         current.insertCharAt(cLength, maxLength - cLength, gapCharacter);
1115         modified = true;
1116       }
1117       else if (current.getLength() > maxLength)
1118       {
1119         current.deleteChars(maxLength, current.getLength());
1120       }
1121     }
1122     return modified;
1123   }
1124
1125   /**
1126    * Justify the sequences to the left or right by deleting and inserting gaps
1127    * before the initial residue or after the terminal residue
1128    * 
1129    * @param right
1130    *          true if alignment padded to right, false to justify to left
1131    * @return true if alignment was changed
1132    */
1133   @Override
1134   public boolean justify(boolean right)
1135   {
1136     boolean modified = false;
1137
1138     // Remove excess gaps from the end of alignment
1139     int maxLength = -1;
1140     int ends[] = new int[sequences.size() * 2];
1141     SequenceI current;
1142     for (int i = 0; i < sequences.size(); i++)
1143     {
1144       current = getSequenceAt(i);
1145       // This should really be a sequence method
1146       ends[i * 2] = current.findIndex(current.getStart());
1147       ends[i * 2 + 1] = current.findIndex(current.getStart()
1148               + current.getLength());
1149       boolean hitres = false;
1150       for (int j = 0, rs = 0, ssiz = current.getLength(); j < ssiz; j++)
1151       {
1152         if (!jalview.util.Comparison.isGap(current.getCharAt(j)))
1153         {
1154           if (!hitres)
1155           {
1156             ends[i * 2] = j;
1157             hitres = true;
1158           }
1159           else
1160           {
1161             ends[i * 2 + 1] = j;
1162             if (j - ends[i * 2] > maxLength)
1163             {
1164               maxLength = j - ends[i * 2];
1165             }
1166           }
1167         }
1168       }
1169     }
1170
1171     maxLength++;
1172     // now edit the flanking gaps to justify to either left or right
1173     int cLength, extent, diff;
1174     for (int i = 0; i < sequences.size(); i++)
1175     {
1176       current = getSequenceAt(i);
1177
1178       cLength = 1 + ends[i * 2 + 1] - ends[i * 2];
1179       diff = maxLength - cLength; // number of gaps to indent
1180       extent = current.getLength();
1181       if (right)
1182       {
1183         // right justify
1184         if (extent > ends[i * 2 + 1])
1185         {
1186           current.deleteChars(ends[i * 2 + 1] + 1, extent);
1187           modified = true;
1188         }
1189         if (ends[i * 2] > diff)
1190         {
1191           current.deleteChars(0, ends[i * 2] - diff);
1192           modified = true;
1193         }
1194         else
1195         {
1196           if (ends[i * 2] < diff)
1197           {
1198             current.insertCharAt(0, diff - ends[i * 2], gapCharacter);
1199             modified = true;
1200           }
1201         }
1202       }
1203       else
1204       {
1205         // left justify
1206         if (ends[i * 2] > 0)
1207         {
1208           current.deleteChars(0, ends[i * 2]);
1209           modified = true;
1210           ends[i * 2 + 1] -= ends[i * 2];
1211           extent -= ends[i * 2];
1212         }
1213         if (extent > maxLength)
1214         {
1215           current.deleteChars(maxLength + 1, extent);
1216           modified = true;
1217         }
1218         else
1219         {
1220           if (extent < maxLength)
1221           {
1222             current.insertCharAt(extent, maxLength - extent, gapCharacter);
1223             modified = true;
1224           }
1225         }
1226       }
1227     }
1228     return modified;
1229   }
1230
1231   @Override
1232   public HiddenSequences getHiddenSequences()
1233   {
1234     return hiddenSequences;
1235   }
1236
1237   @Override
1238   public CigarArray getCompactAlignment()
1239   {
1240     synchronized (sequences)
1241     {
1242       SeqCigar alseqs[] = new SeqCigar[sequences.size()];
1243       int i = 0;
1244       for (SequenceI seq : sequences)
1245       {
1246         alseqs[i++] = new SeqCigar(seq);
1247       }
1248       CigarArray cal = new CigarArray(alseqs);
1249       cal.addOperation(CigarArray.M, getWidth());
1250       return cal;
1251     }
1252   }
1253
1254   @Override
1255   public void setProperty(Object key, Object value)
1256   {
1257     if (alignmentProperties == null)
1258     {
1259       alignmentProperties = new Hashtable();
1260     }
1261
1262     alignmentProperties.put(key, value);
1263   }
1264
1265   @Override
1266   public Object getProperty(Object key)
1267   {
1268     if (alignmentProperties != null)
1269     {
1270       return alignmentProperties.get(key);
1271     }
1272     else
1273     {
1274       return null;
1275     }
1276   }
1277
1278   @Override
1279   public Hashtable getProperties()
1280   {
1281     return alignmentProperties;
1282   }
1283
1284   /**
1285    * Adds the given mapping to the stored set. Note this may be held on the
1286    * dataset alignment.
1287    */
1288   @Override
1289   public void addCodonFrame(AlignedCodonFrame codons)
1290   {
1291     List<AlignedCodonFrame> acfs = getCodonFrames();
1292     if (codons != null && acfs != null && !acfs.contains(codons))
1293     {
1294       acfs.add(codons);
1295     }
1296   }
1297
1298   /*
1299    * (non-Javadoc)
1300    * 
1301    * @see
1302    * jalview.datamodel.AlignmentI#getCodonFrame(jalview.datamodel.SequenceI)
1303    */
1304   @Override
1305   public List<AlignedCodonFrame> getCodonFrame(SequenceI seq)
1306   {
1307     if (seq == null)
1308     {
1309       return null;
1310     }
1311     List<AlignedCodonFrame> cframes = new ArrayList<AlignedCodonFrame>();
1312     for (AlignedCodonFrame acf : getCodonFrames())
1313     {
1314       if (acf.involvesSequence(seq))
1315       {
1316         cframes.add(acf);
1317       }
1318     }
1319     return cframes;
1320   }
1321
1322   /**
1323    * Sets the codon frame mappings (replacing any existing mappings). Note the
1324    * mappings are set on the dataset alignment instead if there is one.
1325    * 
1326    * @see jalview.datamodel.AlignmentI#setCodonFrames()
1327    */
1328   @Override
1329   public void setCodonFrames(List<AlignedCodonFrame> acfs)
1330   {
1331     if (dataset != null)
1332     {
1333       dataset.setCodonFrames(acfs);
1334     }
1335     else
1336     {
1337       this.codonFrameList = acfs;
1338     }
1339   }
1340
1341   /**
1342    * Returns the set of codon frame mappings. Any changes to the returned set
1343    * will affect the alignment. The mappings are held on (and read from) the
1344    * dataset alignment if there is one.
1345    * 
1346    * @see jalview.datamodel.AlignmentI#getCodonFrames()
1347    */
1348   @Override
1349   public List<AlignedCodonFrame> getCodonFrames()
1350   {
1351     // TODO: Fix this method to fix failing AlignedCodonFrame tests
1352     // this behaviour is currently incorrect. method should return codon frames
1353     // for just the alignment,
1354     // selected from dataset
1355     return dataset != null ? dataset.getCodonFrames() : codonFrameList;
1356   }
1357
1358   /**
1359    * Removes the given mapping from the stored set. Note that the mappings are
1360    * held on the dataset alignment if there is one.
1361    */
1362   @Override
1363   public boolean removeCodonFrame(AlignedCodonFrame codons)
1364   {
1365     List<AlignedCodonFrame> acfs = getCodonFrames();
1366     if (codons == null || acfs == null)
1367     {
1368       return false;
1369     }
1370     return acfs.remove(codons);
1371   }
1372
1373   @Override
1374   public void append(AlignmentI toappend)
1375   {
1376     if (toappend == this)
1377     {
1378       System.err.println("Self append may cause a deadlock.");
1379     }
1380     // TODO test this method for a future 2.5 release
1381     // currently tested for use in jalview.gui.SequenceFetcher
1382     boolean samegap = toappend.getGapCharacter() == getGapCharacter();
1383     char oldc = toappend.getGapCharacter();
1384     boolean hashidden = toappend.getHiddenSequences() != null
1385             && toappend.getHiddenSequences().hiddenSequences != null;
1386     // get all sequences including any hidden ones
1387     List<SequenceI> sqs = (hashidden) ? toappend.getHiddenSequences()
1388             .getFullAlignment().getSequences() : toappend.getSequences();
1389     if (sqs != null)
1390     {
1391       synchronized (sqs)
1392       {
1393         for (SequenceI addedsq : sqs)
1394         {
1395           if (!samegap)
1396           {
1397             char[] oldseq = addedsq.getSequence();
1398             for (int c = 0; c < oldseq.length; c++)
1399             {
1400               if (oldseq[c] == oldc)
1401               {
1402                 oldseq[c] = gapCharacter;
1403               }
1404             }
1405           }
1406           addSequence(addedsq);
1407         }
1408       }
1409     }
1410     AlignmentAnnotation[] alan = toappend.getAlignmentAnnotation();
1411     for (int a = 0; alan != null && a < alan.length; a++)
1412     {
1413       addAnnotation(alan[a]);
1414     }
1415
1416     // use add method
1417     getCodonFrames().addAll(toappend.getCodonFrames());
1418
1419     List<SequenceGroup> sg = toappend.getGroups();
1420     if (sg != null)
1421     {
1422       for (SequenceGroup _sg : sg)
1423       {
1424         addGroup(_sg);
1425       }
1426     }
1427     if (toappend.getHiddenSequences() != null)
1428     {
1429       HiddenSequences hs = toappend.getHiddenSequences();
1430       if (hiddenSequences == null)
1431       {
1432         hiddenSequences = new HiddenSequences(this);
1433       }
1434       if (hs.hiddenSequences != null)
1435       {
1436         for (int s = 0; s < hs.hiddenSequences.length; s++)
1437         {
1438           // hide the newly appended sequence in the alignment
1439           if (hs.hiddenSequences[s] != null)
1440           {
1441             hiddenSequences.hideSequence(hs.hiddenSequences[s]);
1442           }
1443         }
1444       }
1445     }
1446     if (toappend.getProperties() != null)
1447     {
1448       // we really can't do very much here - just try to concatenate strings
1449       // where property collisions occur.
1450       Enumeration key = toappend.getProperties().keys();
1451       while (key.hasMoreElements())
1452       {
1453         Object k = key.nextElement();
1454         Object ourval = this.getProperty(k);
1455         Object toapprop = toappend.getProperty(k);
1456         if (ourval != null)
1457         {
1458           if (ourval.getClass().equals(toapprop.getClass())
1459                   && !ourval.equals(toapprop))
1460           {
1461             if (ourval instanceof String)
1462             {
1463               // append strings
1464               this.setProperty(k, ((String) ourval) + "; "
1465                       + ((String) toapprop));
1466             }
1467             else
1468             {
1469               if (ourval instanceof Vector)
1470               {
1471                 // append vectors
1472                 Enumeration theirv = ((Vector) toapprop).elements();
1473                 while (theirv.hasMoreElements())
1474                 {
1475                   ((Vector) ourval).addElement(theirv);
1476                 }
1477               }
1478             }
1479           }
1480         }
1481         else
1482         {
1483           // just add new property directly
1484           setProperty(k, toapprop);
1485         }
1486
1487       }
1488     }
1489   }
1490
1491   @Override
1492   public AlignmentAnnotation findOrCreateAnnotation(String name,
1493           String calcId, boolean autoCalc, SequenceI seqRef,
1494           SequenceGroup groupRef)
1495   {
1496     assert (name != null);
1497     if (annotations != null)
1498     {
1499       for (AlignmentAnnotation annot : getAlignmentAnnotation())
1500       {
1501         if (annot.autoCalculated == autoCalc && (name.equals(annot.label))
1502                 && (calcId == null || annot.getCalcId().equals(calcId))
1503                 && annot.sequenceRef == seqRef
1504                 && annot.groupRef == groupRef)
1505         {
1506           return annot;
1507         }
1508       }
1509     }
1510     AlignmentAnnotation annot = new AlignmentAnnotation(name, name,
1511             new Annotation[1], 0f, 0f, AlignmentAnnotation.BAR_GRAPH);
1512     annot.hasText = false;
1513     annot.setCalcId(new String(calcId));
1514     annot.autoCalculated = autoCalc;
1515     if (seqRef != null)
1516     {
1517       annot.setSequenceRef(seqRef);
1518     }
1519     annot.groupRef = groupRef;
1520     addAnnotation(annot);
1521
1522     return annot;
1523   }
1524
1525   @Override
1526   public Iterable<AlignmentAnnotation> findAnnotation(String calcId)
1527   {
1528     ArrayList<AlignmentAnnotation> aa = new ArrayList<AlignmentAnnotation>();
1529     for (AlignmentAnnotation a : getAlignmentAnnotation())
1530     {
1531       if (a.getCalcId() == calcId
1532               || (a.getCalcId() != null && calcId != null && a.getCalcId()
1533                       .equals(calcId)))
1534       {
1535         aa.add(a);
1536       }
1537     }
1538     return aa;
1539   }
1540
1541   /**
1542    * Returns an iterable collection of any annotations that match on given
1543    * sequence ref, calcId and label (ignoring null values).
1544    */
1545   @Override
1546   public Iterable<AlignmentAnnotation> findAnnotations(SequenceI seq,
1547           String calcId, String label)
1548   {
1549     ArrayList<AlignmentAnnotation> aa = new ArrayList<AlignmentAnnotation>();
1550     for (AlignmentAnnotation ann : getAlignmentAnnotation())
1551     {
1552       if (ann.getCalcId() != null && ann.getCalcId().equals(calcId)
1553               && ann.sequenceRef != null && ann.sequenceRef == seq
1554               && ann.label != null && ann.label.equals(label))
1555       {
1556         aa.add(ann);
1557       }
1558     }
1559     return aa;
1560   }
1561
1562   @Override
1563   public void moveSelectedSequencesByOne(SequenceGroup sg,
1564           Map<SequenceI, SequenceCollectionI> map, boolean up)
1565   {
1566     synchronized (sequences)
1567     {
1568       if (up)
1569       {
1570
1571         for (int i = 1, iSize = sequences.size(); i < iSize; i++)
1572         {
1573           SequenceI seq = sequences.get(i);
1574           if (!sg.getSequences(map).contains(seq))
1575           {
1576             continue;
1577           }
1578
1579           SequenceI temp = sequences.get(i - 1);
1580           if (sg.getSequences(null).contains(temp))
1581           {
1582             continue;
1583           }
1584
1585           sequences.set(i, temp);
1586           sequences.set(i - 1, seq);
1587         }
1588       }
1589       else
1590       {
1591         for (int i = sequences.size() - 2; i > -1; i--)
1592         {
1593           SequenceI seq = sequences.get(i);
1594           if (!sg.getSequences(map).contains(seq))
1595           {
1596             continue;
1597           }
1598
1599           SequenceI temp = sequences.get(i + 1);
1600           if (sg.getSequences(map).contains(temp))
1601           {
1602             continue;
1603           }
1604
1605           sequences.set(i, temp);
1606           sequences.set(i + 1, seq);
1607         }
1608       }
1609
1610     }
1611   }
1612
1613   @Override
1614   public void validateAnnotation(AlignmentAnnotation alignmentAnnotation)
1615   {
1616     alignmentAnnotation.validateRangeAndDisplay();
1617     if (isNucleotide() && alignmentAnnotation.isValidStruc())
1618     {
1619       hasRNAStructure = true;
1620     }
1621   }
1622
1623   private SequenceI seqrep = null;
1624
1625   /**
1626    * 
1627    * @return the representative sequence for this group
1628    */
1629   @Override
1630   public SequenceI getSeqrep()
1631   {
1632     return seqrep;
1633   }
1634
1635   /**
1636    * set the representative sequence for this group. Note - this affects the
1637    * interpretation of the Hidereps attribute.
1638    * 
1639    * @param seqrep
1640    *          the seqrep to set (null means no sequence representative)
1641    */
1642   @Override
1643   public void setSeqrep(SequenceI seqrep)
1644   {
1645     this.seqrep = seqrep;
1646   }
1647
1648   /**
1649    * 
1650    * @return true if group has a sequence representative
1651    */
1652   @Override
1653   public boolean hasSeqrep()
1654   {
1655     return seqrep != null;
1656   }
1657
1658   @Override
1659   public int getEndRes()
1660   {
1661     return getWidth() - 1;
1662   }
1663
1664   @Override
1665   public int getStartRes()
1666   {
1667     return 0;
1668   }
1669
1670   /*
1671    * In the case of AlignmentI - returns the dataset for the alignment, if set
1672    * (non-Javadoc)
1673    * 
1674    * @see jalview.datamodel.AnnotatedCollectionI#getContext()
1675    */
1676   @Override
1677   public AnnotatedCollectionI getContext()
1678   {
1679     return dataset;
1680   }
1681
1682   /**
1683    * Align this alignment like the given (mapped) one.
1684    */
1685   @Override
1686   public int alignAs(AlignmentI al)
1687   {
1688     /*
1689      * Currently retains unmapped gaps (in introns), regaps mapped regions
1690      * (exons)
1691      */
1692     return alignAs(al, false, true);
1693   }
1694
1695   /**
1696    * Align this alignment 'the same as' the given one. Mapped sequences only are
1697    * realigned. If both of the same type (nucleotide/protein) then align both
1698    * identically. If this is nucleotide and the other is protein, make 3 gaps
1699    * for each gap in the protein sequences. If this is protein and the other is
1700    * nucleotide, insert a gap for each 3 gaps (or part thereof) between
1701    * nucleotide bases. If this is protein and the other is nucleotide, gaps
1702    * protein to match the relative ordering of codons in the nucleotide.
1703    * 
1704    * Parameters control whether gaps in exon (mapped) and intron (unmapped)
1705    * regions are preserved. Gaps that connect introns to exons are treated
1706    * conservatively, i.e. only preserved if both intron and exon gaps are
1707    * preserved. TODO: check caveats below where the implementation fails
1708    * 
1709    * @param al
1710    *          - must have same dataset, and sequences in al must have equivalent
1711    *          dataset sequence and start/end bounds under given mapping
1712    * @param preserveMappedGaps
1713    *          if true, gaps within and between mapped codons are preserved
1714    * @param preserveUnmappedGaps
1715    *          if true, gaps within and between unmapped codons are preserved
1716    */
1717   // @Override
1718   public int alignAs(AlignmentI al, boolean preserveMappedGaps,
1719           boolean preserveUnmappedGaps)
1720   {
1721     // TODO should this method signature be the one in the interface?
1722     boolean thisIsNucleotide = this.isNucleotide();
1723     boolean thatIsProtein = !al.isNucleotide();
1724     if (!thatIsProtein && !thisIsNucleotide)
1725     {
1726       return AlignmentUtils.alignProteinAsDna(this, al);
1727     }
1728     else if (thatIsProtein && thisIsNucleotide)
1729     {
1730       return AlignmentUtils.alignCdsAsProtein(this, al);
1731     }
1732     return AlignmentUtils.alignAs(this, al);
1733   }
1734
1735   /**
1736    * Returns the alignment in Fasta format. Behaviour of this method is not
1737    * guaranteed between versions.
1738    */
1739   @Override
1740   public String toString()
1741   {
1742     return new FastaFile().print(getSequencesArray());
1743   }
1744
1745   /**
1746    * Returns the set of distinct sequence names. No ordering is guaranteed.
1747    */
1748   @Override
1749   public Set<String> getSequenceNames()
1750   {
1751     Set<String> names = new HashSet<String>();
1752     for (SequenceI seq : getSequences())
1753     {
1754       names.add(seq.getName());
1755     }
1756     return names;
1757   }
1758
1759   @Override
1760   public boolean hasValidSequence()
1761   {
1762     boolean hasValidSeq = false;
1763     for (SequenceI seq : getSequences())
1764     {
1765       if ((seq.getEnd() - seq.getStart()) > 0)
1766       {
1767         hasValidSeq = true;
1768         break;
1769       }
1770     }
1771     return hasValidSeq;
1772   }
1773
1774   /**
1775    * Update any mappings to 'virtual' sequences to compatible real ones, if
1776    * present in the added sequences. Returns a count of mappings updated.
1777    * 
1778    * @param seqs
1779    * @return
1780    */
1781   @Override
1782   public int realiseMappings(List<SequenceI> seqs)
1783   {
1784     int count = 0;
1785     for (SequenceI seq : seqs)
1786     {
1787       for (AlignedCodonFrame mapping : getCodonFrames())
1788       {
1789         count += mapping.realiseWith(seq);
1790       }
1791     }
1792     return count;
1793   }
1794
1795   /**
1796    * Returns the first AlignedCodonFrame that has a mapping between the given
1797    * dataset sequences
1798    * 
1799    * @param mapFrom
1800    * @param mapTo
1801    * @return
1802    */
1803   @Override
1804   public AlignedCodonFrame getMapping(SequenceI mapFrom, SequenceI mapTo)
1805   {
1806     for (AlignedCodonFrame acf : getCodonFrames())
1807     {
1808       if (acf.getAaForDnaSeq(mapFrom) == mapTo)
1809       {
1810         return acf;
1811       }
1812     }
1813     return null;
1814   }
1815
1816   @Override
1817   public int[] getVisibleStartAndEndIndex(List<int[]> hiddenCols)
1818   {
1819     int[] alignmentStartEnd = new int[] { 0, getWidth() - 1 };
1820     int startPos = alignmentStartEnd[0];
1821     int endPos = alignmentStartEnd[1];
1822
1823     int[] lowestRange = new int[] { -1, -1 };
1824     int[] higestRange = new int[] { -1, -1 };
1825
1826     for (int[] hiddenCol : hiddenCols)
1827     {
1828       lowestRange = (hiddenCol[0] <= startPos) ? hiddenCol : lowestRange;
1829       higestRange = (hiddenCol[1] >= endPos) ? hiddenCol : higestRange;
1830     }
1831
1832     if (lowestRange[0] == -1 && lowestRange[1] == -1)
1833     {
1834       startPos = alignmentStartEnd[0];
1835     }
1836     else
1837     {
1838       startPos = lowestRange[1] + 1;
1839     }
1840
1841     if (higestRange[0] == -1 && higestRange[1] == -1)
1842     {
1843       endPos = alignmentStartEnd[1];
1844     }
1845     else
1846     {
1847       endPos = higestRange[0] - 1;
1848     }
1849     return new int[] { startPos, endPos };
1850   }
1851 }