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