Merge branch 'develop' into features/JAL-2360colourSchemeApplicability
[jalview.git] / src / jalview / datamodel / Alignment.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.datamodel;
22
23 import jalview.analysis.AlignmentUtils;
24 import jalview.datamodel.AlignedCodonFrame.SequenceToSequenceMapping;
25 import jalview.io.FastaFile;
26 import jalview.util.Comparison;
27 import jalview.util.LinkedIdentityHashSet;
28 import jalview.util.MessageManager;
29
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.Enumeration;
33 import java.util.HashSet;
34 import java.util.Hashtable;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Set;
38 import java.util.Vector;
39
40 /**
41  * Data structure to hold and manipulate a multiple sequence alignment
42  */
43 /**
44  * @author JimP
45  * 
46  */
47 public class Alignment implements AlignmentI
48 {
49   private Alignment dataset;
50
51   protected List<SequenceI> sequences;
52
53   protected List<SequenceGroup> groups;
54
55   protected char gapCharacter = '-';
56
57   private boolean nucleotide = true;
58
59   public boolean hasRNAStructure = false;
60
61   public AlignmentAnnotation[] annotations;
62
63   HiddenSequences hiddenSequences;
64
65   public Hashtable alignmentProperties;
66
67   private List<AlignedCodonFrame> codonFrameList;
68
69   private void initAlignment(SequenceI[] seqs)
70   {
71     groups = Collections.synchronizedList(new ArrayList<SequenceGroup>());
72     hiddenSequences = new HiddenSequences(this);
73     codonFrameList = new ArrayList<AlignedCodonFrame>();
74
75     nucleotide = Comparison.isNucleotide(seqs);
76
77     sequences = Collections.synchronizedList(new ArrayList<SequenceI>());
78
79     for (int i = 0; i < seqs.length; i++)
80     {
81       sequences.add(seqs[i]);
82     }
83
84   }
85
86   /**
87    * Make a 'copy' alignment - sequences have new copies of features and
88    * annotations, but share the original dataset sequences.
89    */
90   public Alignment(AlignmentI al)
91   {
92     SequenceI[] seqs = al.getSequencesArray();
93     for (int i = 0; i < seqs.length; i++)
94     {
95       seqs[i] = new Sequence(seqs[i]);
96     }
97
98     initAlignment(seqs);
99
100     /*
101      * Share the same dataset sequence mappings (if any). 
102      */
103     if (dataset == null && al.getDataset() == null)
104     {
105       this.setCodonFrames(al.getCodonFrames());
106     }
107   }
108
109   /**
110    * Make an alignment from an array of Sequences.
111    * 
112    * @param sequences
113    */
114   public Alignment(SequenceI[] seqs)
115   {
116     initAlignment(seqs);
117   }
118
119   /**
120    * Make a new alignment from an array of SeqCigars
121    * 
122    * @param seqs
123    *          SeqCigar[]
124    */
125   public Alignment(SeqCigar[] alseqs)
126   {
127     SequenceI[] seqs = SeqCigar.createAlignmentSequences(alseqs,
128             gapCharacter, new ColumnSelection(), null);
129     initAlignment(seqs);
130   }
131
132   /**
133    * Make a new alignment from an CigarArray JBPNote - can only do this when
134    * compactAlignment does not contain hidden regions. JBPNote - must also check
135    * that compactAlignment resolves to a set of SeqCigars - or construct them
136    * appropriately.
137    * 
138    * @param compactAlignment
139    *          CigarArray
140    */
141   public static AlignmentI createAlignment(CigarArray compactAlignment)
142   {
143     throw new Error(
144             MessageManager
145                     .getString("error.alignment_cigararray_not_implemented"));
146     // this(compactAlignment.refCigars);
147   }
148
149   @Override
150   public List<SequenceI> getSequences()
151   {
152     return sequences;
153   }
154
155   @Override
156   public List<SequenceI> getSequences(
157           Map<SequenceI, SequenceCollectionI> hiddenReps)
158   {
159     // TODO: in jalview 2.8 we don't do anything with hiddenreps - fix design to
160     // work on this.
161     return sequences;
162   }
163
164   @Override
165   public SequenceI[] getSequencesArray()
166   {
167     if (sequences == null)
168     {
169       return null;
170     }
171     synchronized (sequences)
172     {
173       return sequences.toArray(new SequenceI[sequences.size()]);
174     }
175   }
176
177   /**
178    * Returns a map of lists of sequences keyed by sequence name.
179    * 
180    * @return
181    */
182   @Override
183   public Map<String, List<SequenceI>> getSequencesByName()
184   {
185     return AlignmentUtils.getSequencesByName(this);
186   }
187
188   /**
189    * DOCUMENT ME!
190    * 
191    * @param i
192    *          DOCUMENT ME!
193    * 
194    * @return DOCUMENT ME!
195    */
196   @Override
197   public SequenceI getSequenceAt(int i)
198   {
199     synchronized (sequences)
200     {
201       if (i > -1 && i < sequences.size())
202       {
203         return sequences.get(i);
204       }
205     }
206     return null;
207   }
208
209   /**
210    * Adds a sequence to the alignment. Recalculates maxLength and size. Note
211    * this currently does not recalculate whether or not the alignment is
212    * nucleotide, so mixed alignments may have undefined behaviour.
213    * 
214    * @param snew
215    */
216   @Override
217   public void addSequence(SequenceI snew)
218   {
219     if (dataset != null)
220     {
221
222       // maintain dataset integrity
223       SequenceI dsseq = snew.getDatasetSequence();
224       if (dsseq == null)
225       {
226         // derive new sequence
227         SequenceI adding = snew.deriveSequence();
228         snew = adding;
229         dsseq = snew.getDatasetSequence();
230       }
231       if (getDataset().findIndex(dsseq) == -1)
232       {
233         getDataset().addSequence(dsseq);
234       }
235
236     }
237     if (sequences == null)
238     {
239       initAlignment(new SequenceI[] { snew });
240     }
241     else
242     {
243       synchronized (sequences)
244       {
245         sequences.add(snew);
246       }
247     }
248     if (hiddenSequences != null)
249     {
250       hiddenSequences.adjustHeightSequenceAdded();
251     }
252   }
253
254   @Override
255   public SequenceI replaceSequenceAt(int i, SequenceI snew)
256   {
257     synchronized (sequences)
258     {
259       if (sequences.size() > i)
260       {
261         return sequences.set(i, snew);
262
263       }
264       else
265       {
266         sequences.add(snew);
267         hiddenSequences.adjustHeightSequenceAdded();
268       }
269       return null;
270     }
271   }
272
273   /**
274    * DOCUMENT ME!
275    * 
276    * @return DOCUMENT ME!
277    */
278   @Override
279   public List<SequenceGroup> getGroups()
280   {
281     return groups;
282   }
283
284   @Override
285   public void finalize() throws Throwable
286   {
287     if (getDataset() != null)
288     {
289       getDataset().removeAlignmentRef();
290     }
291
292     nullReferences();
293     super.finalize();
294   }
295
296   /**
297    * Defensively nulls out references in case this object is not garbage
298    * collected
299    */
300   void nullReferences()
301   {
302     dataset = null;
303     sequences = null;
304     groups = null;
305     annotations = null;
306     hiddenSequences = null;
307   }
308
309   /**
310    * decrement the alignmentRefs counter by one and null references if it goes
311    * to zero.
312    * 
313    * @throws Throwable
314    */
315   private void removeAlignmentRef() throws Throwable
316   {
317     if (--alignmentRefs == 0)
318     {
319       nullReferences();
320     }
321   }
322
323   /**
324    * DOCUMENT ME!
325    * 
326    * @param s
327    *          DOCUMENT ME!
328    */
329   @Override
330   public void deleteSequence(SequenceI s)
331   {
332     deleteSequence(findIndex(s));
333   }
334
335   /**
336    * DOCUMENT ME!
337    * 
338    * @param i
339    *          DOCUMENT ME!
340    */
341   @Override
342   public void deleteSequence(int i)
343   {
344     if (i > -1 && i < getHeight())
345     {
346       synchronized (sequences)
347       {
348         sequences.remove(i);
349         hiddenSequences.adjustHeightSequenceDeleted(i);
350       }
351     }
352   }
353
354   /*
355    * (non-Javadoc)
356    * 
357    * @see jalview.datamodel.AlignmentI#findGroup(jalview.datamodel.SequenceI)
358    */
359   @Override
360   public SequenceGroup findGroup(SequenceI seq, int position)
361   {
362     synchronized (groups)
363     {
364       for (SequenceGroup sg : groups)
365       {
366         if (sg.getSequences(null).contains(seq))
367         {
368           if (position >= sg.getStartRes() && position <= sg.getEndRes())
369           {
370             return sg;
371           }
372         }
373       }
374     }
375     return null;
376   }
377
378   /*
379    * (non-Javadoc)
380    * 
381    * @see
382    * jalview.datamodel.AlignmentI#findAllGroups(jalview.datamodel.SequenceI)
383    */
384   @Override
385   public SequenceGroup[] findAllGroups(SequenceI s)
386   {
387     ArrayList<SequenceGroup> temp = new ArrayList<SequenceGroup>();
388
389     synchronized (groups)
390     {
391       int gSize = groups.size();
392       for (int i = 0; i < gSize; i++)
393       {
394         SequenceGroup sg = groups.get(i);
395         if (sg == null || sg.getSequences() == null)
396         {
397           this.deleteGroup(sg);
398           gSize--;
399           continue;
400         }
401
402         if (sg.getSequences().contains(s))
403         {
404           temp.add(sg);
405         }
406       }
407     }
408     SequenceGroup[] ret = new SequenceGroup[temp.size()];
409     return temp.toArray(ret);
410   }
411
412   /**    */
413   @Override
414   public void addGroup(SequenceGroup sg)
415   {
416     synchronized (groups)
417     {
418       if (!groups.contains(sg))
419       {
420         if (hiddenSequences.getSize() > 0)
421         {
422           int i, iSize = sg.getSize();
423           for (i = 0; i < iSize; i++)
424           {
425             if (!sequences.contains(sg.getSequenceAt(i)))
426             {
427               sg.deleteSequence(sg.getSequenceAt(i), false);
428               iSize--;
429               i--;
430             }
431           }
432
433           if (sg.getSize() < 1)
434           {
435             return;
436           }
437         }
438         sg.setContext(this);
439         groups.add(sg);
440       }
441     }
442   }
443
444   /**
445    * remove any annotation that references gp
446    * 
447    * @param gp
448    *          (if null, removes all group associated annotation)
449    */
450   private void removeAnnotationForGroup(SequenceGroup gp)
451   {
452     if (annotations == null || annotations.length == 0)
453     {
454       return;
455     }
456     // remove annotation very quickly
457     AlignmentAnnotation[] t, todelete = new AlignmentAnnotation[annotations.length], tokeep = new AlignmentAnnotation[annotations.length];
458     int i, p, k;
459     if (gp == null)
460     {
461       for (i = 0, p = 0, k = 0; i < annotations.length; i++)
462       {
463         if (annotations[i].groupRef != null)
464         {
465           todelete[p++] = annotations[i];
466         }
467         else
468         {
469           tokeep[k++] = annotations[i];
470         }
471       }
472     }
473     else
474     {
475       for (i = 0, p = 0, k = 0; i < annotations.length; i++)
476       {
477         if (annotations[i].groupRef == gp)
478         {
479           todelete[p++] = annotations[i];
480         }
481         else
482         {
483           tokeep[k++] = annotations[i];
484         }
485       }
486     }
487     if (p > 0)
488     {
489       // clear out the group associated annotation.
490       for (i = 0; i < p; i++)
491       {
492         unhookAnnotation(todelete[i]);
493         todelete[i] = null;
494       }
495       t = new AlignmentAnnotation[k];
496       for (i = 0; i < k; i++)
497       {
498         t[i] = tokeep[i];
499       }
500       annotations = t;
501     }
502   }
503
504   @Override
505   public void deleteAllGroups()
506   {
507     synchronized (groups)
508     {
509       if (annotations != null)
510       {
511         removeAnnotationForGroup(null);
512       }
513       for (SequenceGroup sg : groups)
514       {
515         sg.setContext(null);
516       }
517       groups.clear();
518     }
519   }
520
521   /**    */
522   @Override
523   public void deleteGroup(SequenceGroup g)
524   {
525     synchronized (groups)
526     {
527       if (groups.contains(g))
528       {
529         removeAnnotationForGroup(g);
530         groups.remove(g);
531         g.setContext(null);
532       }
533     }
534   }
535
536   /**    */
537   @Override
538   public SequenceI findName(String name)
539   {
540     return findName(name, false);
541   }
542
543   /*
544    * (non-Javadoc)
545    * 
546    * @see jalview.datamodel.AlignmentI#findName(java.lang.String, boolean)
547    */
548   @Override
549   public SequenceI findName(String token, boolean b)
550   {
551     return findName(null, token, b);
552   }
553
554   /*
555    * (non-Javadoc)
556    * 
557    * @see jalview.datamodel.AlignmentI#findName(SequenceI, java.lang.String,
558    * boolean)
559    */
560   @Override
561   public SequenceI findName(SequenceI startAfter, String token, boolean b)
562   {
563
564     int i = 0;
565     SequenceI sq = null;
566     String sqname = null;
567     if (startAfter != null)
568     {
569       // try to find the sequence in the alignment
570       boolean matched = false;
571       while (i < sequences.size())
572       {
573         if (getSequenceAt(i++) == startAfter)
574         {
575           matched = true;
576           break;
577         }
578       }
579       if (!matched)
580       {
581         i = 0;
582       }
583     }
584     while (i < sequences.size())
585     {
586       sq = getSequenceAt(i);
587       sqname = sq.getName();
588       if (sqname.equals(token) // exact match
589               || (b && // allow imperfect matches - case varies
590               (sqname.equalsIgnoreCase(token))))
591       {
592         return getSequenceAt(i);
593       }
594
595       i++;
596     }
597
598     return null;
599   }
600
601   @Override
602   public SequenceI[] findSequenceMatch(String name)
603   {
604     Vector matches = new Vector();
605     int i = 0;
606
607     while (i < sequences.size())
608     {
609       if (getSequenceAt(i).getName().equals(name))
610       {
611         matches.addElement(getSequenceAt(i));
612       }
613       i++;
614     }
615
616     SequenceI[] result = new SequenceI[matches.size()];
617     for (i = 0; i < result.length; i++)
618     {
619       result[i] = (SequenceI) matches.elementAt(i);
620     }
621
622     return result;
623
624   }
625
626   /*
627    * (non-Javadoc)
628    * 
629    * @see jalview.datamodel.AlignmentI#findIndex(jalview.datamodel.SequenceI)
630    */
631   @Override
632   public int findIndex(SequenceI s)
633   {
634     int i = 0;
635
636     while (i < sequences.size())
637     {
638       if (s == getSequenceAt(i))
639       {
640         return i;
641       }
642
643       i++;
644     }
645
646     return -1;
647   }
648
649   /*
650    * (non-Javadoc)
651    * 
652    * @see
653    * jalview.datamodel.AlignmentI#findIndex(jalview.datamodel.SearchResults)
654    */
655   @Override
656   public int findIndex(SearchResultsI results)
657   {
658     int i = 0;
659
660     while (i < sequences.size())
661     {
662       if (results.involvesSequence(getSequenceAt(i)))
663       {
664         return i;
665       }
666       i++;
667     }
668     return -1;
669   }
670
671   /**
672    * DOCUMENT ME!
673    * 
674    * @return DOCUMENT ME!
675    */
676   @Override
677   public int getHeight()
678   {
679     return sequences.size();
680   }
681
682   /**
683    * DOCUMENT ME!
684    * 
685    * @return DOCUMENT ME!
686    */
687   @Override
688   public int getWidth()
689   {
690     int maxLength = -1;
691
692     for (int i = 0; i < sequences.size(); i++)
693     {
694       if (getSequenceAt(i).getLength() > maxLength)
695       {
696         maxLength = getSequenceAt(i).getLength();
697       }
698     }
699
700     return maxLength;
701   }
702
703   /**
704    * DOCUMENT ME!
705    * 
706    * @param gc
707    *          DOCUMENT ME!
708    */
709   @Override
710   public void setGapCharacter(char gc)
711   {
712     gapCharacter = gc;
713     synchronized (sequences)
714     {
715       for (SequenceI seq : sequences)
716       {
717         seq.setSequence(seq.getSequenceAsString().replace('.', gc)
718                 .replace('-', gc).replace(' ', gc));
719       }
720     }
721   }
722
723   /**
724    * DOCUMENT ME!
725    * 
726    * @return DOCUMENT ME!
727    */
728   @Override
729   public char getGapCharacter()
730   {
731     return gapCharacter;
732   }
733
734   /*
735    * (non-Javadoc)
736    * 
737    * @see jalview.datamodel.AlignmentI#isAligned()
738    */
739   @Override
740   public boolean isAligned()
741   {
742     return isAligned(false);
743   }
744
745   /*
746    * (non-Javadoc)
747    * 
748    * @see jalview.datamodel.AlignmentI#isAligned(boolean)
749    */
750   @Override
751   public boolean isAligned(boolean includeHidden)
752   {
753     int width = getWidth();
754     if (hiddenSequences == null || hiddenSequences.getSize() == 0)
755     {
756       includeHidden = true; // no hidden sequences to check against.
757     }
758     for (int i = 0; i < sequences.size(); i++)
759     {
760       if (includeHidden || !hiddenSequences.isHidden(getSequenceAt(i)))
761       {
762         if (getSequenceAt(i).getLength() != width)
763         {
764           return false;
765         }
766       }
767     }
768
769     return true;
770   }
771
772   /**
773    * Delete all annotations, including auto-calculated if the flag is set true.
774    * Returns true if at least one annotation was deleted, else false.
775    * 
776    * @param includingAutoCalculated
777    * @return
778    */
779   @Override
780   public boolean deleteAllAnnotations(boolean includingAutoCalculated)
781   {
782     boolean result = false;
783     for (AlignmentAnnotation alan : getAlignmentAnnotation())
784     {
785       if (!alan.autoCalculated || includingAutoCalculated)
786       {
787         deleteAnnotation(alan);
788         result = true;
789       }
790     }
791     return result;
792   }
793
794   /*
795    * (non-Javadoc)
796    * 
797    * @seejalview.datamodel.AlignmentI#deleteAnnotation(jalview.datamodel.
798    * AlignmentAnnotation)
799    */
800   @Override
801   public boolean deleteAnnotation(AlignmentAnnotation aa)
802   {
803     return deleteAnnotation(aa, true);
804   }
805
806   @Override
807   public boolean deleteAnnotation(AlignmentAnnotation aa, boolean unhook)
808   {
809     int aSize = 1;
810
811     if (annotations != null)
812     {
813       aSize = annotations.length;
814     }
815
816     if (aSize < 1)
817     {
818       return false;
819     }
820
821     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize - 1];
822
823     boolean swap = false;
824     int tIndex = 0;
825
826     for (int i = 0; i < aSize; i++)
827     {
828       if (annotations[i] == aa)
829       {
830         swap = true;
831         continue;
832       }
833       if (tIndex < temp.length)
834       {
835         temp[tIndex++] = annotations[i];
836       }
837     }
838
839     if (swap)
840     {
841       annotations = temp;
842       if (unhook)
843       {
844         unhookAnnotation(aa);
845       }
846     }
847     return swap;
848   }
849
850   /**
851    * remove any object references associated with this annotation
852    * 
853    * @param aa
854    */
855   private void unhookAnnotation(AlignmentAnnotation aa)
856   {
857     if (aa.sequenceRef != null)
858     {
859       aa.sequenceRef.removeAlignmentAnnotation(aa);
860     }
861     if (aa.groupRef != null)
862     {
863       // probably need to do more here in the future (post 2.5.0)
864       aa.groupRef = null;
865     }
866   }
867
868   /*
869    * (non-Javadoc)
870    * 
871    * @seejalview.datamodel.AlignmentI#addAnnotation(jalview.datamodel.
872    * AlignmentAnnotation)
873    */
874   @Override
875   public void addAnnotation(AlignmentAnnotation aa)
876   {
877     addAnnotation(aa, -1);
878   }
879
880   /*
881    * (non-Javadoc)
882    * 
883    * @seejalview.datamodel.AlignmentI#addAnnotation(jalview.datamodel.
884    * AlignmentAnnotation, int)
885    */
886   @Override
887   public void addAnnotation(AlignmentAnnotation aa, int pos)
888   {
889     if (aa.getRNAStruc() != null)
890     {
891       hasRNAStructure = true;
892     }
893
894     int aSize = 1;
895     if (annotations != null)
896     {
897       aSize = annotations.length + 1;
898     }
899
900     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize];
901     int i = 0;
902     if (pos == -1 || pos >= aSize)
903     {
904       temp[aSize - 1] = aa;
905     }
906     else
907     {
908       temp[pos] = aa;
909     }
910     if (aSize > 1)
911     {
912       int p = 0;
913       for (i = 0; i < (aSize - 1); i++, p++)
914       {
915         if (p == pos)
916         {
917           p++;
918         }
919         if (p < temp.length)
920         {
921           temp[p] = annotations[i];
922         }
923       }
924     }
925
926     annotations = temp;
927   }
928
929   @Override
930   public void setAnnotationIndex(AlignmentAnnotation aa, int index)
931   {
932     if (aa == null || annotations == null || annotations.length - 1 < index)
933     {
934       return;
935     }
936
937     int aSize = annotations.length;
938     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize];
939
940     temp[index] = aa;
941
942     for (int i = 0; i < aSize; i++)
943     {
944       if (i == index)
945       {
946         continue;
947       }
948
949       if (i < index)
950       {
951         temp[i] = annotations[i];
952       }
953       else
954       {
955         temp[i] = annotations[i - 1];
956       }
957     }
958
959     annotations = temp;
960   }
961
962   @Override
963   /**
964    * returns all annotation on the alignment
965    */
966   public AlignmentAnnotation[] getAlignmentAnnotation()
967   {
968     return annotations;
969   }
970
971   @Override
972   public boolean isNucleotide()
973   {
974     return nucleotide;
975   }
976
977   @Override
978   public boolean hasRNAStructure()
979   {
980     // TODO can it happen that structure is removed from alignment?
981     return hasRNAStructure;
982   }
983
984   @Override
985   public void setDataset(AlignmentI data)
986   {
987     if (dataset == null && data == null)
988     {
989       createDatasetAlignment();
990     }
991     else if (dataset == null && data != null)
992     {
993       if (!(data instanceof Alignment))
994       {
995         throw new Error(
996                 "Implementation Error: jalview.datamodel.Alignment does not yet support other implementations of AlignmentI as its dataset reference");
997       }
998       dataset = (Alignment) data;
999       for (int i = 0; i < getHeight(); i++)
1000       {
1001         SequenceI currentSeq = getSequenceAt(i);
1002         SequenceI dsq = currentSeq.getDatasetSequence();
1003         if (dsq == null)
1004         {
1005           dsq = currentSeq.createDatasetSequence();
1006           dataset.addSequence(dsq);
1007         }
1008         else
1009         {
1010           while (dsq.getDatasetSequence() != null)
1011           {
1012             dsq = dsq.getDatasetSequence();
1013           }
1014           if (dataset.findIndex(dsq) == -1)
1015           {
1016             dataset.addSequence(dsq);
1017           }
1018         }
1019       }
1020     }
1021     dataset.addAlignmentRef();
1022   }
1023
1024   /**
1025    * add dataset sequences to seq for currentSeq and any sequences it references
1026    */
1027   private void resolveAndAddDatasetSeq(SequenceI currentSeq,
1028           Set<SequenceI> seqs, boolean createDatasetSequence)
1029   {
1030     SequenceI alignedSeq = currentSeq;
1031     if (currentSeq.getDatasetSequence() != null)
1032     {
1033       currentSeq = currentSeq.getDatasetSequence();
1034     }
1035     else
1036     {
1037       if (createDatasetSequence)
1038       {
1039         currentSeq = currentSeq.createDatasetSequence();
1040       }
1041     }
1042     if (seqs.contains(currentSeq))
1043     {
1044       return;
1045     }
1046     List<SequenceI> toProcess = new ArrayList<SequenceI>();
1047     toProcess.add(currentSeq);
1048     while (toProcess.size() > 0)
1049     {
1050       // use a queue ?
1051       SequenceI curDs = toProcess.remove(0);
1052       if (seqs.contains(curDs))
1053       {
1054         continue;
1055       }
1056       seqs.add(curDs);
1057       // iterate over database references, making sure we add forward referenced
1058       // sequences
1059       if (curDs.getDBRefs() != null)
1060       {
1061         for (DBRefEntry dbr : curDs.getDBRefs())
1062         {
1063           if (dbr.getMap() != null && dbr.getMap().getTo() != null)
1064           {
1065             if (dbr.getMap().getTo() == alignedSeq)
1066             {
1067               /*
1068                * update mapping to be to the newly created dataset sequence
1069                */
1070               dbr.getMap().setTo(currentSeq);
1071             }
1072             if (dbr.getMap().getTo().getDatasetSequence() != null)
1073             {
1074               throw new Error(
1075                       "Implementation error: Map.getTo() for dbref " + dbr
1076                               + " from " + curDs.getName()
1077                               + " is not a 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 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     if (annotations != null)
1566     {
1567       for (AlignmentAnnotation annot : getAlignmentAnnotation())
1568       {
1569         if (annot.autoCalculated == autoCalc && (name.equals(annot.label))
1570                 && (calcId == null || annot.getCalcId().equals(calcId))
1571                 && annot.sequenceRef == seqRef
1572                 && annot.groupRef == groupRef)
1573         {
1574           return annot;
1575         }
1576       }
1577     }
1578     AlignmentAnnotation annot = new AlignmentAnnotation(name, name,
1579             new Annotation[1], 0f, 0f, AlignmentAnnotation.BAR_GRAPH);
1580     annot.hasText = false;
1581     annot.setCalcId(new String(calcId));
1582     annot.autoCalculated = autoCalc;
1583     if (seqRef != null)
1584     {
1585       annot.setSequenceRef(seqRef);
1586     }
1587     annot.groupRef = groupRef;
1588     addAnnotation(annot);
1589
1590     return annot;
1591   }
1592
1593   @Override
1594   public Iterable<AlignmentAnnotation> findAnnotation(String calcId)
1595   {
1596     List<AlignmentAnnotation> aa = new ArrayList<AlignmentAnnotation>();
1597     AlignmentAnnotation[] alignmentAnnotation = getAlignmentAnnotation();
1598     if (alignmentAnnotation != null)
1599     {
1600       for (AlignmentAnnotation a : alignmentAnnotation)
1601       {
1602         if (a.getCalcId() == calcId
1603                 || (a.getCalcId() != null && calcId != null && a
1604                         .getCalcId().equals(calcId)))
1605         {
1606           aa.add(a);
1607         }
1608       }
1609     }
1610     return aa;
1611   }
1612
1613   /**
1614    * Returns an iterable collection of any annotations that match on given
1615    * sequence ref, calcId and label (ignoring null values).
1616    */
1617   @Override
1618   public Iterable<AlignmentAnnotation> findAnnotations(SequenceI seq,
1619           String calcId, String label)
1620   {
1621     ArrayList<AlignmentAnnotation> aa = new ArrayList<AlignmentAnnotation>();
1622     for (AlignmentAnnotation ann : getAlignmentAnnotation())
1623     {
1624       if (ann.getCalcId() != null && ann.getCalcId().equals(calcId)
1625               && ann.sequenceRef != null && ann.sequenceRef == seq
1626               && ann.label != null && ann.label.equals(label))
1627       {
1628         aa.add(ann);
1629       }
1630     }
1631     return aa;
1632   }
1633
1634   @Override
1635   public void moveSelectedSequencesByOne(SequenceGroup sg,
1636           Map<SequenceI, SequenceCollectionI> map, boolean up)
1637   {
1638     synchronized (sequences)
1639     {
1640       if (up)
1641       {
1642
1643         for (int i = 1, iSize = sequences.size(); i < iSize; i++)
1644         {
1645           SequenceI seq = sequences.get(i);
1646           if (!sg.getSequences(map).contains(seq))
1647           {
1648             continue;
1649           }
1650
1651           SequenceI temp = sequences.get(i - 1);
1652           if (sg.getSequences(null).contains(temp))
1653           {
1654             continue;
1655           }
1656
1657           sequences.set(i, temp);
1658           sequences.set(i - 1, seq);
1659         }
1660       }
1661       else
1662       {
1663         for (int i = sequences.size() - 2; i > -1; i--)
1664         {
1665           SequenceI seq = sequences.get(i);
1666           if (!sg.getSequences(map).contains(seq))
1667           {
1668             continue;
1669           }
1670
1671           SequenceI temp = sequences.get(i + 1);
1672           if (sg.getSequences(map).contains(temp))
1673           {
1674             continue;
1675           }
1676
1677           sequences.set(i, temp);
1678           sequences.set(i + 1, seq);
1679         }
1680       }
1681
1682     }
1683   }
1684
1685   @Override
1686   public void validateAnnotation(AlignmentAnnotation alignmentAnnotation)
1687   {
1688     alignmentAnnotation.validateRangeAndDisplay();
1689     if (isNucleotide() && alignmentAnnotation.isValidStruc())
1690     {
1691       hasRNAStructure = true;
1692     }
1693   }
1694
1695   private SequenceI seqrep = null;
1696
1697   /**
1698    * 
1699    * @return the representative sequence for this group
1700    */
1701   @Override
1702   public SequenceI getSeqrep()
1703   {
1704     return seqrep;
1705   }
1706
1707   /**
1708    * set the representative sequence for this group. Note - this affects the
1709    * interpretation of the Hidereps attribute.
1710    * 
1711    * @param seqrep
1712    *          the seqrep to set (null means no sequence representative)
1713    */
1714   @Override
1715   public void setSeqrep(SequenceI seqrep)
1716   {
1717     this.seqrep = seqrep;
1718   }
1719
1720   /**
1721    * 
1722    * @return true if group has a sequence representative
1723    */
1724   @Override
1725   public boolean hasSeqrep()
1726   {
1727     return seqrep != null;
1728   }
1729
1730   @Override
1731   public int getEndRes()
1732   {
1733     return getWidth() - 1;
1734   }
1735
1736   @Override
1737   public int getStartRes()
1738   {
1739     return 0;
1740   }
1741
1742   /*
1743    * In the case of AlignmentI - returns the dataset for the alignment, if set
1744    * (non-Javadoc)
1745    * 
1746    * @see jalview.datamodel.AnnotatedCollectionI#getContext()
1747    */
1748   @Override
1749   public AnnotatedCollectionI getContext()
1750   {
1751     return dataset;
1752   }
1753
1754   /**
1755    * Align this alignment like the given (mapped) one.
1756    */
1757   @Override
1758   public int alignAs(AlignmentI al)
1759   {
1760     /*
1761      * Currently retains unmapped gaps (in introns), regaps mapped regions
1762      * (exons)
1763      */
1764     return alignAs(al, false, true);
1765   }
1766
1767   /**
1768    * Align this alignment 'the same as' the given one. Mapped sequences only are
1769    * realigned. If both of the same type (nucleotide/protein) then align both
1770    * identically. If this is nucleotide and the other is protein, make 3 gaps
1771    * for each gap in the protein sequences. If this is protein and the other is
1772    * nucleotide, insert a gap for each 3 gaps (or part thereof) between
1773    * nucleotide bases. If this is protein and the other is nucleotide, gaps
1774    * protein to match the relative ordering of codons in the nucleotide.
1775    * 
1776    * Parameters control whether gaps in exon (mapped) and intron (unmapped)
1777    * regions are preserved. Gaps that connect introns to exons are treated
1778    * conservatively, i.e. only preserved if both intron and exon gaps are
1779    * preserved. TODO: check caveats below where the implementation fails
1780    * 
1781    * @param al
1782    *          - must have same dataset, and sequences in al must have equivalent
1783    *          dataset sequence and start/end bounds under given mapping
1784    * @param preserveMappedGaps
1785    *          if true, gaps within and between mapped codons are preserved
1786    * @param preserveUnmappedGaps
1787    *          if true, gaps within and between unmapped codons are preserved
1788    */
1789   // @Override
1790   public int alignAs(AlignmentI al, boolean preserveMappedGaps,
1791           boolean preserveUnmappedGaps)
1792   {
1793     // TODO should this method signature be the one in the interface?
1794     // JBPComment - yes - neither flag is used, so should be deleted.
1795     boolean thisIsNucleotide = this.isNucleotide();
1796     boolean thatIsProtein = !al.isNucleotide();
1797     if (!thatIsProtein && !thisIsNucleotide)
1798     {
1799       return AlignmentUtils.alignProteinAsDna(this, al);
1800     }
1801     else if (thatIsProtein && thisIsNucleotide)
1802     {
1803       return AlignmentUtils.alignCdsAsProtein(this, al);
1804     }
1805     return AlignmentUtils.alignAs(this, al);
1806   }
1807
1808   /**
1809    * Returns the alignment in Fasta format. Behaviour of this method is not
1810    * guaranteed between versions.
1811    */
1812   @Override
1813   public String toString()
1814   {
1815     return new FastaFile().print(getSequencesArray(), true);
1816   }
1817
1818   /**
1819    * Returns the set of distinct sequence names. No ordering is guaranteed.
1820    */
1821   @Override
1822   public Set<String> getSequenceNames()
1823   {
1824     Set<String> names = new HashSet<String>();
1825     for (SequenceI seq : getSequences())
1826     {
1827       names.add(seq.getName());
1828     }
1829     return names;
1830   }
1831
1832   @Override
1833   public boolean hasValidSequence()
1834   {
1835     boolean hasValidSeq = false;
1836     for (SequenceI seq : getSequences())
1837     {
1838       if ((seq.getEnd() - seq.getStart()) > 0)
1839       {
1840         hasValidSeq = true;
1841         break;
1842       }
1843     }
1844     return hasValidSeq;
1845   }
1846
1847   /**
1848    * Update any mappings to 'virtual' sequences to compatible real ones, if
1849    * present in the added sequences. Returns a count of mappings updated.
1850    * 
1851    * @param seqs
1852    * @return
1853    */
1854   @Override
1855   public int realiseMappings(List<SequenceI> seqs)
1856   {
1857     int count = 0;
1858     for (SequenceI seq : seqs)
1859     {
1860       for (AlignedCodonFrame mapping : getCodonFrames())
1861       {
1862         count += mapping.realiseWith(seq);
1863       }
1864     }
1865     return count;
1866   }
1867
1868   /**
1869    * Returns the first AlignedCodonFrame that has a mapping between the given
1870    * dataset sequences
1871    * 
1872    * @param mapFrom
1873    * @param mapTo
1874    * @return
1875    */
1876   @Override
1877   public AlignedCodonFrame getMapping(SequenceI mapFrom, SequenceI mapTo)
1878   {
1879     for (AlignedCodonFrame acf : getCodonFrames())
1880     {
1881       if (acf.getAaForDnaSeq(mapFrom) == mapTo)
1882       {
1883         return acf;
1884       }
1885     }
1886     return null;
1887   }
1888
1889   @Override
1890   public int[] getVisibleStartAndEndIndex(List<int[]> hiddenCols)
1891   {
1892     int[] alignmentStartEnd = new int[] { 0, getWidth() - 1 };
1893     int startPos = alignmentStartEnd[0];
1894     int endPos = alignmentStartEnd[1];
1895
1896     int[] lowestRange = new int[] { -1, -1 };
1897     int[] higestRange = new int[] { -1, -1 };
1898
1899     for (int[] hiddenCol : hiddenCols)
1900     {
1901       lowestRange = (hiddenCol[0] <= startPos) ? hiddenCol : lowestRange;
1902       higestRange = (hiddenCol[1] >= endPos) ? hiddenCol : higestRange;
1903     }
1904
1905     if (lowestRange[0] == -1 && lowestRange[1] == -1)
1906     {
1907       startPos = alignmentStartEnd[0];
1908     }
1909     else
1910     {
1911       startPos = lowestRange[1] + 1;
1912     }
1913
1914     if (higestRange[0] == -1 && higestRange[1] == -1)
1915     {
1916       endPos = alignmentStartEnd[1];
1917     }
1918     else
1919     {
1920       endPos = higestRange[0] - 1;
1921     }
1922     return new int[] { startPos, endPos };
1923   }
1924 }