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