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