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