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