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