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