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