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