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