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