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