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