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