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