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