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