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