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