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