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