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