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