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