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