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