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