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