JAL-3066 JAL-3070 pull up non-jabaws specific service descriptor components for annot...
[jalview.git] / src / jalview / datamodel / Alignment.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.datamodel;
22
23 import jalview.analysis.AlignmentUtils;
24 import jalview.datamodel.AlignedCodonFrame.SequenceToSequenceMapping;
25 import jalview.io.FastaFile;
26 import jalview.util.Comparison;
27 import jalview.util.LinkedIdentityHashSet;
28 import jalview.util.MessageManager;
29
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.BitSet;
33 import java.util.Collections;
34 import java.util.Enumeration;
35 import java.util.HashSet;
36 import java.util.Hashtable;
37 import java.util.Iterator;
38 import java.util.List;
39 import java.util.Map;
40 import java.util.Set;
41 import java.util.Vector;
42
43 /**
44  * Data structure to hold and manipulate a multiple sequence alignment
45  */
46 /**
47  * @author JimP
48  * 
49  */
50 public class Alignment implements AlignmentI, AutoCloseable
51 {
52   private Alignment dataset;
53
54   private List<SequenceI> sequences;
55
56   protected List<SequenceGroup> groups;
57
58   protected char gapCharacter = '-';
59
60   private boolean nucleotide = true;
61
62   public boolean hasRNAStructure = false;
63
64   public AlignmentAnnotation[] annotations;
65
66   HiddenSequences hiddenSequences;
67
68   HiddenColumns hiddenCols;
69
70   public Hashtable alignmentProperties;
71
72   private List<AlignedCodonFrame> codonFrameList;
73
74   private void initAlignment(SequenceI[] seqs)
75   {
76     groups = Collections.synchronizedList(new ArrayList<SequenceGroup>());
77     hiddenSequences = new HiddenSequences(this);
78     hiddenCols = new HiddenColumns();
79     codonFrameList = new ArrayList<>();
80
81     nucleotide = Comparison.isNucleotide(seqs);
82
83     sequences = Collections.synchronizedList(new ArrayList<SequenceI>());
84
85     for (int i = 0; i < seqs.length; i++)
86     {
87       sequences.add(seqs[i]);
88     }
89
90   }
91
92   /**
93    * Make a 'copy' alignment - sequences have new copies of features and
94    * annotations, but share the original dataset sequences.
95    */
96   public Alignment(AlignmentI al)
97   {
98     SequenceI[] seqs = al.getSequencesArray();
99     for (int i = 0; i < seqs.length; i++)
100     {
101       seqs[i] = new Sequence(seqs[i]);
102     }
103
104     initAlignment(seqs);
105
106     /*
107      * Share the same dataset sequence mappings (if any). 
108      */
109     if (dataset == null && al.getDataset() == null)
110     {
111       this.setCodonFrames(al.getCodonFrames());
112     }
113   }
114
115   /**
116    * Make an alignment from an array of Sequences.
117    * 
118    * @param sequences
119    */
120   public Alignment(SequenceI[] seqs)
121   {
122     initAlignment(seqs);
123   }
124
125   /**
126    * Make a new alignment from an array of SeqCigars
127    * 
128    * @param seqs
129    *          SeqCigar[]
130    */
131   public Alignment(SeqCigar[] alseqs)
132   {
133     SequenceI[] seqs = SeqCigar.createAlignmentSequences(alseqs,
134             gapCharacter, new HiddenColumns(), null);
135     initAlignment(seqs);
136   }
137
138   /**
139    * Make a new alignment from an CigarArray JBPNote - can only do this when
140    * compactAlignment does not contain hidden regions. JBPNote - must also check
141    * that compactAlignment resolves to a set of SeqCigars - or construct them
142    * appropriately.
143    * 
144    * @param compactAlignment
145    *          CigarArray
146    */
147   public static AlignmentI createAlignment(CigarArray compactAlignment)
148   {
149     throw new Error(MessageManager
150             .getString("error.alignment_cigararray_not_implemented"));
151     // this(compactAlignment.refCigars);
152   }
153
154   @Override
155   public List<SequenceI> getSequences()
156   {
157     return sequences;
158   }
159
160   @Override
161   public List<SequenceI> getSequences(
162           Map<SequenceI, SequenceCollectionI> hiddenReps)
163   {
164     // TODO: in jalview 2.8 we don't do anything with hiddenreps - fix design to
165     // work on this.
166     return sequences;
167   }
168
169   @Override
170   public SequenceI[] getSequencesArray()
171   {
172     if (sequences == null)
173     {
174       return null;
175     }
176     synchronized (sequences)
177     {
178       return sequences.toArray(new SequenceI[sequences.size()]);
179     }
180   }
181
182   /**
183    * Returns a map of lists of sequences keyed by sequence name.
184    * 
185    * @return
186    */
187   @Override
188   public Map<String, List<SequenceI>> getSequencesByName()
189   {
190     return AlignmentUtils.getSequencesByName(this);
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
204     return null;
205   }
206
207   @Override
208   public SequenceI getSequenceAtAbsoluteIndex(int i)
209   {
210     SequenceI seq = null;
211     if (getHiddenSequences().getSize() > 0)
212     {
213       seq = getHiddenSequences().getHiddenSequence(i);
214       if (seq == null)
215       {
216         // didn't find the sequence in the hidden sequences, get it from the
217         // alignment
218         int index = getHiddenSequences().findIndexWithoutHiddenSeqs(i);
219         seq = getSequenceAt(index);
220       }
221     }
222     else
223     {
224       seq = getSequenceAt(i);
225     }
226     return seq;
227   }
228
229   /**
230    * Adds a sequence to the alignment. Recalculates maxLength and size. Note
231    * this currently does not recalculate whether or not the alignment is
232    * nucleotide, so mixed alignments may have undefined behaviour.
233    * 
234    * @param snew
235    */
236   @Override
237   public void addSequence(SequenceI snew)
238   {
239     if (dataset != null)
240     {
241
242       // maintain dataset integrity
243       SequenceI dsseq = snew.getDatasetSequence();
244       if (dsseq == null)
245       {
246         // derive new sequence
247         SequenceI adding = snew.deriveSequence();
248         snew = adding;
249         dsseq = snew.getDatasetSequence();
250       }
251       if (getDataset().findIndex(dsseq) == -1)
252       {
253         getDataset().addSequence(dsseq);
254       }
255
256     }
257     if (sequences == null)
258     {
259       initAlignment(new SequenceI[] { snew });
260     }
261     else
262     {
263       synchronized (sequences)
264       {
265         sequences.add(snew);
266       }
267     }
268     if (hiddenSequences != null)
269     {
270       hiddenSequences.adjustHeightSequenceAdded();
271     }
272   }
273
274   @Override
275   public SequenceI replaceSequenceAt(int i, SequenceI snew)
276   {
277     synchronized (sequences)
278     {
279       if (sequences.size() > i)
280       {
281         return sequences.set(i, snew);
282
283       }
284       else
285       {
286         sequences.add(snew);
287         hiddenSequences.adjustHeightSequenceAdded();
288       }
289       return null;
290     }
291   }
292
293   /**
294    * Inserts a sequence at a point in the alignment.
295    * 
296    * @param i
297    *          the index of the position the sequence is to be inserted in.
298    */
299   @Override
300   public void insertSequenceAt(int i, SequenceI snew)
301   {
302     synchronized (sequences)
303     {
304       if (sequences.size() > i)
305       {
306         sequences.add(i, snew);
307         return;
308
309       }
310       else
311       {
312         sequences.add(snew);
313         hiddenSequences.adjustHeightSequenceAdded();
314       }
315       return;
316     }
317   }
318
319   /**
320    * DOCUMENT ME!
321    * 
322    * @return DOCUMENT ME!
323    */
324   @Override
325   public List<SequenceGroup> getGroups()
326   {
327     return groups;
328   }
329
330   @Override
331   public void close()
332   {
333     if (getDataset() != null)
334     {
335       try
336       {
337         getDataset().removeAlignmentRef();
338       } catch (Throwable e)
339       {
340         e.printStackTrace();
341       }
342     }
343
344     nullReferences();
345   }
346
347   /**
348    * Defensively nulls out references in case this object is not garbage
349    * collected
350    */
351   void nullReferences()
352   {
353     dataset = null;
354     sequences = null;
355     groups = null;
356     annotations = null;
357     hiddenSequences = null;
358   }
359
360   /**
361    * decrement the alignmentRefs counter by one and null references if it goes
362    * to zero.
363    * 
364    * @throws Throwable
365    */
366   private void removeAlignmentRef() throws Throwable
367   {
368     if (--alignmentRefs == 0)
369     {
370       nullReferences();
371     }
372   }
373
374   @Override
375   public void deleteSequence(SequenceI s)
376   {
377     synchronized (sequences)
378     {
379       deleteSequence(findIndex(s));
380     }
381   }
382
383   @Override
384   public void deleteSequence(int i)
385   {
386     synchronized (sequences)
387     {
388       if (i > -1 && i < getHeight())
389       {
390         sequences.remove(i);
391         hiddenSequences.adjustHeightSequenceDeleted(i);
392       }
393     }
394   }
395
396   @Override
397   public void deleteHiddenSequence(int i)
398   {
399     synchronized (sequences)
400     {
401       if (i > -1 && i < getHeight())
402       {
403         sequences.remove(i);
404       }
405     }
406   }
407
408   /*
409    * (non-Javadoc)
410    * 
411    * @see jalview.datamodel.AlignmentI#findGroup(jalview.datamodel.SequenceI)
412    */
413   @Override
414   public SequenceGroup findGroup(SequenceI seq, int position)
415   {
416     synchronized (groups)
417     {
418       for (SequenceGroup sg : groups)
419       {
420         if (sg.getSequences(null).contains(seq))
421         {
422           if (position >= sg.getStartRes() && position <= sg.getEndRes())
423           {
424             return sg;
425           }
426         }
427       }
428     }
429     return null;
430   }
431
432   /*
433    * (non-Javadoc)
434    * 
435    * @see
436    * jalview.datamodel.AlignmentI#findAllGroups(jalview.datamodel.SequenceI)
437    */
438   @Override
439   public SequenceGroup[] findAllGroups(SequenceI s)
440   {
441     ArrayList<SequenceGroup> temp = new ArrayList<>();
442
443     synchronized (groups)
444     {
445       int gSize = groups.size();
446       for (int i = 0; i < gSize; i++)
447       {
448         SequenceGroup sg = groups.get(i);
449         if (sg == null || sg.getSequences() == null)
450         {
451           this.deleteGroup(sg);
452           gSize--;
453           continue;
454         }
455
456         if (sg.getSequences().contains(s))
457         {
458           temp.add(sg);
459         }
460       }
461     }
462     SequenceGroup[] ret = new SequenceGroup[temp.size()];
463     return temp.toArray(ret);
464   }
465
466   /**    */
467   @Override
468   public void addGroup(SequenceGroup sg)
469   {
470     synchronized (groups)
471     {
472       if (!groups.contains(sg))
473       {
474         if (hiddenSequences.getSize() > 0)
475         {
476           int i, iSize = sg.getSize();
477           for (i = 0; i < iSize; i++)
478           {
479             if (!sequences.contains(sg.getSequenceAt(i)))
480             {
481               sg.deleteSequence(sg.getSequenceAt(i), false);
482               iSize--;
483               i--;
484             }
485           }
486
487           if (sg.getSize() < 1)
488           {
489             return;
490           }
491         }
492         sg.setContext(this, true);
493         groups.add(sg);
494       }
495     }
496   }
497
498   /**
499    * remove any annotation that references gp
500    * 
501    * @param gp
502    *          (if null, removes all group associated annotation)
503    */
504   private void removeAnnotationForGroup(SequenceGroup gp)
505   {
506     if (annotations == null || annotations.length == 0)
507     {
508       return;
509     }
510     // remove annotation very quickly
511     AlignmentAnnotation[] t,
512             todelete = new AlignmentAnnotation[annotations.length],
513             tokeep = new AlignmentAnnotation[annotations.length];
514     int i, p, k;
515     if (gp == null)
516     {
517       for (i = 0, p = 0, k = 0; i < annotations.length; i++)
518       {
519         if (annotations[i].groupRef != null)
520         {
521           todelete[p++] = annotations[i];
522         }
523         else
524         {
525           tokeep[k++] = annotations[i];
526         }
527       }
528     }
529     else
530     {
531       for (i = 0, p = 0, k = 0; i < annotations.length; i++)
532       {
533         if (annotations[i].groupRef == gp)
534         {
535           todelete[p++] = annotations[i];
536         }
537         else
538         {
539           tokeep[k++] = annotations[i];
540         }
541       }
542     }
543     if (p > 0)
544     {
545       // clear out the group associated annotation.
546       for (i = 0; i < p; i++)
547       {
548         unhookAnnotation(todelete[i]);
549         todelete[i] = null;
550       }
551       t = new AlignmentAnnotation[k];
552       for (i = 0; i < k; i++)
553       {
554         t[i] = tokeep[i];
555       }
556       annotations = t;
557     }
558   }
559
560   @Override
561   public void deleteAllGroups()
562   {
563     synchronized (groups)
564     {
565       if (annotations != null)
566       {
567         removeAnnotationForGroup(null);
568       }
569       for (SequenceGroup sg : groups)
570       {
571         sg.setContext(null, false);
572       }
573       groups.clear();
574     }
575   }
576
577   /**    */
578   @Override
579   public void deleteGroup(SequenceGroup g)
580   {
581     synchronized (groups)
582     {
583       if (groups.contains(g))
584       {
585         removeAnnotationForGroup(g);
586         groups.remove(g);
587         g.setContext(null, false);
588       }
589     }
590   }
591
592   /**    */
593   @Override
594   public SequenceI findName(String name)
595   {
596     return findName(name, false);
597   }
598
599   /*
600    * (non-Javadoc)
601    * 
602    * @see jalview.datamodel.AlignmentI#findName(java.lang.String, boolean)
603    */
604   @Override
605   public SequenceI findName(String token, boolean b)
606   {
607     return findName(null, token, b);
608   }
609
610   /*
611    * (non-Javadoc)
612    * 
613    * @see jalview.datamodel.AlignmentI#findName(SequenceI, java.lang.String,
614    * boolean)
615    */
616   @Override
617   public SequenceI findName(SequenceI startAfter, String token, boolean b)
618   {
619
620     int i = 0;
621     SequenceI sq = null;
622     String sqname = null;
623     if (startAfter != null)
624     {
625       // try to find the sequence in the alignment
626       boolean matched = false;
627       while (i < sequences.size())
628       {
629         if (getSequenceAt(i++) == startAfter)
630         {
631           matched = true;
632           break;
633         }
634       }
635       if (!matched)
636       {
637         i = 0;
638       }
639     }
640     while (i < sequences.size())
641     {
642       sq = getSequenceAt(i);
643       sqname = sq.getName();
644       if (sqname.equals(token) // exact match
645               || (b && // allow imperfect matches - case varies
646                       (sqname.equalsIgnoreCase(token))))
647       {
648         return getSequenceAt(i);
649       }
650
651       i++;
652     }
653
654     return null;
655   }
656
657   @Override
658   public SequenceI[] findSequenceMatch(String name)
659   {
660     Vector matches = new Vector();
661     int i = 0;
662
663     while (i < sequences.size())
664     {
665       if (getSequenceAt(i).getName().equals(name))
666       {
667         matches.addElement(getSequenceAt(i));
668       }
669       i++;
670     }
671
672     SequenceI[] result = new SequenceI[matches.size()];
673     for (i = 0; i < result.length; i++)
674     {
675       result[i] = (SequenceI) matches.elementAt(i);
676     }
677
678     return result;
679
680   }
681
682   /*
683    * (non-Javadoc)
684    * 
685    * @see jalview.datamodel.AlignmentI#findIndex(jalview.datamodel.SequenceI)
686    */
687   @Override
688   public int findIndex(SequenceI s)
689   {
690     int i = 0;
691
692     while (i < sequences.size())
693     {
694       if (s == getSequenceAt(i))
695       {
696         return i;
697       }
698
699       i++;
700     }
701
702     return -1;
703   }
704
705   /*
706    * (non-Javadoc)
707    * 
708    * @see
709    * jalview.datamodel.AlignmentI#findIndex(jalview.datamodel.SearchResults)
710    */
711   @Override
712   public int findIndex(SearchResultsI results)
713   {
714     int i = 0;
715
716     while (i < sequences.size())
717     {
718       if (results.involvesSequence(getSequenceAt(i)))
719       {
720         return i;
721       }
722       i++;
723     }
724     return -1;
725   }
726
727   @Override
728   public int getHeight()
729   {
730     return sequences.size();
731   }
732
733   @Override
734   public int getAbsoluteHeight()
735   {
736     return sequences.size() + getHiddenSequences().getSize();
737   }
738
739   @Override
740   public int getWidth()
741   {
742     int maxLength = -1;
743   
744     for (int i = 0; i < sequences.size(); i++)
745     {
746       maxLength = Math.max(maxLength, getSequenceAt(i).getLength());
747     }
748     return maxLength;
749   }
750
751   @Override
752   public int getVisibleWidth()
753   {
754     int w = getWidth();
755     if (hiddenCols != null)
756     {
757       w -= hiddenCols.getSize();
758     }
759     return w;
760   }
761
762   /**
763    * DOCUMENT ME!
764    * 
765    * @param gc
766    *          DOCUMENT ME!
767    */
768   @Override
769   public void setGapCharacter(char gc)
770   {
771     gapCharacter = gc;
772     synchronized (sequences)
773     {
774       for (SequenceI seq : sequences)
775       {
776         seq.setSequence(seq.getSequenceAsString().replace('.', gc)
777                 .replace('-', gc).replace(' ', gc));
778       }
779     }
780   }
781
782   /**
783    * DOCUMENT ME!
784    * 
785    * @return DOCUMENT ME!
786    */
787   @Override
788   public char getGapCharacter()
789   {
790     return gapCharacter;
791   }
792
793   /*
794    * (non-Javadoc)
795    * 
796    * @see jalview.datamodel.AlignmentI#isAligned()
797    */
798   @Override
799   public boolean isAligned()
800   {
801     return isAligned(false);
802   }
803
804   /*
805    * (non-Javadoc)
806    * 
807    * @see jalview.datamodel.AlignmentI#isAligned(boolean)
808    */
809   @Override
810   public boolean isAligned(boolean includeHidden)
811   {
812     int width = getWidth();
813     if (hiddenSequences == null || hiddenSequences.getSize() == 0)
814     {
815       includeHidden = true; // no hidden sequences to check against.
816     }
817     for (int i = 0; i < sequences.size(); i++)
818     {
819       if (includeHidden || !hiddenSequences.isHidden(getSequenceAt(i)))
820       {
821         if (getSequenceAt(i).getLength() != width)
822         {
823           return false;
824         }
825       }
826     }
827
828     return true;
829   }
830
831   @Override
832   public boolean isHidden(int alignmentIndex)
833   {
834     return (getHiddenSequences().getHiddenSequence(alignmentIndex) != null);
835   }
836
837   /**
838    * Delete all annotations, including auto-calculated if the flag is set true.
839    * Returns true if at least one annotation was deleted, else false.
840    * 
841    * @param includingAutoCalculated
842    * @return
843    */
844   @Override
845   public boolean deleteAllAnnotations(boolean includingAutoCalculated)
846   {
847     boolean result = false;
848     for (AlignmentAnnotation alan : getAlignmentAnnotation())
849     {
850       if (!alan.autoCalculated || includingAutoCalculated)
851       {
852         deleteAnnotation(alan);
853         result = true;
854       }
855     }
856     return result;
857   }
858
859   /*
860    * (non-Javadoc)
861    * 
862    * @seejalview.datamodel.AlignmentI#deleteAnnotation(jalview.datamodel.
863    * AlignmentAnnotation)
864    */
865   @Override
866   public boolean deleteAnnotation(AlignmentAnnotation aa)
867   {
868     return deleteAnnotation(aa, true);
869   }
870
871   @Override
872   public boolean deleteAnnotation(AlignmentAnnotation aa, boolean unhook)
873   {
874     int aSize = 1;
875
876     if (annotations != null)
877     {
878       aSize = annotations.length;
879     }
880
881     if (aSize < 1)
882     {
883       return false;
884     }
885
886     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize - 1];
887
888     boolean swap = false;
889     int tIndex = 0;
890
891     for (int i = 0; i < aSize; i++)
892     {
893       if (annotations[i] == aa)
894       {
895         swap = true;
896         continue;
897       }
898       if (tIndex < temp.length)
899       {
900         temp[tIndex++] = annotations[i];
901       }
902     }
903
904     if (swap)
905     {
906       annotations = temp;
907       if (unhook)
908       {
909         unhookAnnotation(aa);
910       }
911     }
912     return swap;
913   }
914
915   /**
916    * remove any object references associated with this annotation
917    * 
918    * @param aa
919    */
920   private void unhookAnnotation(AlignmentAnnotation aa)
921   {
922     if (aa.sequenceRef != null)
923     {
924       aa.sequenceRef.removeAlignmentAnnotation(aa);
925     }
926     if (aa.groupRef != null)
927     {
928       // probably need to do more here in the future (post 2.5.0)
929       aa.groupRef = null;
930     }
931   }
932
933   /*
934    * (non-Javadoc)
935    * 
936    * @seejalview.datamodel.AlignmentI#addAnnotation(jalview.datamodel.
937    * AlignmentAnnotation)
938    */
939   @Override
940   public void addAnnotation(AlignmentAnnotation aa)
941   {
942     addAnnotation(aa, -1);
943   }
944
945   /*
946    * (non-Javadoc)
947    * 
948    * @seejalview.datamodel.AlignmentI#addAnnotation(jalview.datamodel.
949    * AlignmentAnnotation, int)
950    */
951   @Override
952   public void addAnnotation(AlignmentAnnotation aa, int pos)
953   {
954     if (aa.getRNAStruc() != null)
955     {
956       hasRNAStructure = true;
957     }
958
959     int aSize = 1;
960     if (annotations != null)
961     {
962       aSize = annotations.length + 1;
963     }
964
965     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize];
966     int i = 0;
967     if (pos == -1 || pos >= aSize)
968     {
969       temp[aSize - 1] = aa;
970     }
971     else
972     {
973       temp[pos] = aa;
974     }
975     if (aSize > 1)
976     {
977       int p = 0;
978       for (i = 0; i < (aSize - 1); i++, p++)
979       {
980         if (p == pos)
981         {
982           p++;
983         }
984         if (p < temp.length)
985         {
986           temp[p] = annotations[i];
987         }
988       }
989     }
990
991     annotations = temp;
992   }
993
994   @Override
995   public void setAnnotationIndex(AlignmentAnnotation aa, int index)
996   {
997     if (aa == null || annotations == null || annotations.length - 1 < index)
998     {
999       return;
1000     }
1001
1002     int aSize = annotations.length;
1003     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize];
1004
1005     temp[index] = aa;
1006
1007     for (int i = 0; i < aSize; i++)
1008     {
1009       if (i == index)
1010       {
1011         continue;
1012       }
1013
1014       if (i < index)
1015       {
1016         temp[i] = annotations[i];
1017       }
1018       else
1019       {
1020         temp[i] = annotations[i - 1];
1021       }
1022     }
1023
1024     annotations = temp;
1025   }
1026
1027   @Override
1028   /**
1029    * returns all annotation on the alignment
1030    */
1031   public AlignmentAnnotation[] getAlignmentAnnotation()
1032   {
1033     return annotations;
1034   }
1035
1036   @Override
1037   public boolean isNucleotide()
1038   {
1039     return nucleotide;
1040   }
1041
1042   @Override
1043   public boolean hasRNAStructure()
1044   {
1045     // TODO can it happen that structure is removed from alignment?
1046     return hasRNAStructure;
1047   }
1048
1049   @Override
1050   public void setDataset(AlignmentI data)
1051   {
1052     if (dataset == null && data == null)
1053     {
1054       createDatasetAlignment();
1055     }
1056     else if (dataset == null && data != null)
1057     {
1058       if (data == this)
1059       {
1060         throw new IllegalArgumentException("Circular dataset reference");
1061       }
1062       if (!(data instanceof Alignment))
1063       {
1064         throw new Error(
1065                 "Implementation Error: jalview.datamodel.Alignment does not yet support other implementations of AlignmentI as its dataset reference");
1066       }
1067       dataset = (Alignment) data;
1068       for (int i = 0; i < getHeight(); i++)
1069       {
1070         SequenceI currentSeq = getSequenceAt(i);
1071         SequenceI dsq = currentSeq.getDatasetSequence();
1072         if (dsq == null)
1073         {
1074           dsq = currentSeq.createDatasetSequence();
1075           dataset.addSequence(dsq);
1076         }
1077         else
1078         {
1079           while (dsq.getDatasetSequence() != null)
1080           {
1081             dsq = dsq.getDatasetSequence();
1082           }
1083           if (dataset.findIndex(dsq) == -1)
1084           {
1085             dataset.addSequence(dsq);
1086           }
1087         }
1088       }
1089     }
1090     dataset.addAlignmentRef();
1091   }
1092
1093   /**
1094    * add dataset sequences to seq for currentSeq and any sequences it references
1095    */
1096   private void resolveAndAddDatasetSeq(SequenceI currentSeq,
1097           Set<SequenceI> seqs, boolean createDatasetSequence)
1098   {
1099     SequenceI alignedSeq = currentSeq;
1100     if (currentSeq.getDatasetSequence() != null)
1101     {
1102       currentSeq = currentSeq.getDatasetSequence();
1103     }
1104     else
1105     {
1106       if (createDatasetSequence)
1107       {
1108         currentSeq = currentSeq.createDatasetSequence();
1109       }
1110     }
1111
1112     List<SequenceI> toProcess = new ArrayList<>();
1113     toProcess.add(currentSeq);
1114     while (toProcess.size() > 0)
1115     {
1116       // use a queue ?
1117       SequenceI curDs = toProcess.remove(0);
1118
1119       if (!seqs.add(curDs))
1120       {
1121         continue;
1122       }
1123       // iterate over database references, making sure we add forward referenced
1124       // sequences
1125       if (curDs.getDBRefs() != null)
1126       {
1127         for (DBRefEntry dbr : curDs.getDBRefs())
1128         {
1129           if (dbr.getMap() != null && dbr.getMap().getTo() != null)
1130           {
1131             if (dbr.getMap().getTo() == alignedSeq)
1132             {
1133               /*
1134                * update mapping to be to the newly created dataset sequence
1135                */
1136               dbr.getMap().setTo(currentSeq);
1137             }
1138             if (dbr.getMap().getTo().getDatasetSequence() != null)
1139             {
1140               throw new Error("Implementation error: Map.getTo() for dbref "
1141                       + dbr + " from " + curDs.getName()
1142                       + " is not a dataset sequence.");
1143             }
1144             // we recurse to add all forward references to dataset sequences via
1145             // DBRefs/etc
1146             toProcess.add(dbr.getMap().getTo());
1147           }
1148         }
1149       }
1150     }
1151   }
1152
1153   /**
1154    * Creates a new dataset for this alignment. Can only be done once - if
1155    * dataset is not null this will not be performed.
1156    */
1157   public void createDatasetAlignment()
1158   {
1159     if (dataset != null)
1160     {
1161       return;
1162     }
1163     // try to avoid using SequenceI.equals at this stage, it will be expensive
1164     Set<SequenceI> seqs = new LinkedIdentityHashSet<>();
1165
1166     for (int i = 0; i < getHeight(); i++)
1167     {
1168       SequenceI currentSeq = getSequenceAt(i);
1169       resolveAndAddDatasetSeq(currentSeq, seqs, true);
1170     }
1171
1172     // verify all mappings are in dataset
1173     for (AlignedCodonFrame cf : codonFrameList)
1174     {
1175       for (SequenceToSequenceMapping ssm : cf.getMappings())
1176       {
1177         if (!seqs.contains(ssm.getFromSeq()))
1178         {
1179           resolveAndAddDatasetSeq(ssm.getFromSeq(), seqs, false);
1180         }
1181         if (!seqs.contains(ssm.getMapping().getTo()))
1182         {
1183           resolveAndAddDatasetSeq(ssm.getMapping().getTo(), seqs, false);
1184         }
1185       }
1186     }
1187     // finally construct dataset
1188     dataset = new Alignment(seqs.toArray(new SequenceI[seqs.size()]));
1189     // move mappings to the dataset alignment
1190     dataset.codonFrameList = this.codonFrameList;
1191     this.codonFrameList = null;
1192   }
1193
1194   /**
1195    * reference count for number of alignments referencing this one.
1196    */
1197   int alignmentRefs = 0;
1198
1199   /**
1200    * increase reference count to this alignment.
1201    */
1202   private void addAlignmentRef()
1203   {
1204     alignmentRefs++;
1205   }
1206
1207   @Override
1208   public Alignment getDataset()
1209   {
1210     return dataset;
1211   }
1212
1213   @Override
1214   public boolean padGaps()
1215   {
1216     boolean modified = false;
1217
1218     // Remove excess gaps from the end of alignment
1219     int maxLength = -1;
1220
1221     SequenceI current;
1222     for (int i = 0; i < sequences.size(); i++)
1223     {
1224       current = getSequenceAt(i);
1225       for (int j = current.getLength(); j > maxLength; j--)
1226       {
1227         if (j > maxLength
1228                 && !jalview.util.Comparison.isGap(current.getCharAt(j)))
1229         {
1230           maxLength = j;
1231           break;
1232         }
1233       }
1234     }
1235
1236     maxLength++;
1237
1238     int cLength;
1239     for (int i = 0; i < sequences.size(); i++)
1240     {
1241       current = getSequenceAt(i);
1242       cLength = current.getLength();
1243
1244       if (cLength < maxLength)
1245       {
1246         current.insertCharAt(cLength, maxLength - cLength, gapCharacter);
1247         modified = true;
1248       }
1249       else if (current.getLength() > maxLength)
1250       {
1251         current.deleteChars(maxLength, current.getLength());
1252       }
1253     }
1254     return modified;
1255   }
1256
1257   /**
1258    * Justify the sequences to the left or right by deleting and inserting gaps
1259    * before the initial residue or after the terminal residue
1260    * 
1261    * @param right
1262    *          true if alignment padded to right, false to justify to left
1263    * @return true if alignment was changed
1264    */
1265   @Override
1266   public boolean justify(boolean right)
1267   {
1268     boolean modified = false;
1269
1270     // Remove excess gaps from the end of alignment
1271     int maxLength = -1;
1272     int ends[] = new int[sequences.size() * 2];
1273     SequenceI current;
1274     for (int i = 0; i < sequences.size(); i++)
1275     {
1276       current = getSequenceAt(i);
1277       // This should really be a sequence method
1278       ends[i * 2] = current.findIndex(current.getStart());
1279       ends[i * 2 + 1] = current
1280               .findIndex(current.getStart() + current.getLength());
1281       boolean hitres = false;
1282       for (int j = 0, rs = 0, ssiz = current.getLength(); j < ssiz; j++)
1283       {
1284         if (!jalview.util.Comparison.isGap(current.getCharAt(j)))
1285         {
1286           if (!hitres)
1287           {
1288             ends[i * 2] = j;
1289             hitres = true;
1290           }
1291           else
1292           {
1293             ends[i * 2 + 1] = j;
1294             if (j - ends[i * 2] > maxLength)
1295             {
1296               maxLength = j - ends[i * 2];
1297             }
1298           }
1299         }
1300       }
1301     }
1302
1303     maxLength++;
1304     // now edit the flanking gaps to justify to either left or right
1305     int cLength, extent, diff;
1306     for (int i = 0; i < sequences.size(); i++)
1307     {
1308       current = getSequenceAt(i);
1309
1310       cLength = 1 + ends[i * 2 + 1] - ends[i * 2];
1311       diff = maxLength - cLength; // number of gaps to indent
1312       extent = current.getLength();
1313       if (right)
1314       {
1315         // right justify
1316         if (extent > ends[i * 2 + 1])
1317         {
1318           current.deleteChars(ends[i * 2 + 1] + 1, extent);
1319           modified = true;
1320         }
1321         if (ends[i * 2] > diff)
1322         {
1323           current.deleteChars(0, ends[i * 2] - diff);
1324           modified = true;
1325         }
1326         else
1327         {
1328           if (ends[i * 2] < diff)
1329           {
1330             current.insertCharAt(0, diff - ends[i * 2], gapCharacter);
1331             modified = true;
1332           }
1333         }
1334       }
1335       else
1336       {
1337         // left justify
1338         if (ends[i * 2] > 0)
1339         {
1340           current.deleteChars(0, ends[i * 2]);
1341           modified = true;
1342           ends[i * 2 + 1] -= ends[i * 2];
1343           extent -= ends[i * 2];
1344         }
1345         if (extent > maxLength)
1346         {
1347           current.deleteChars(maxLength + 1, extent);
1348           modified = true;
1349         }
1350         else
1351         {
1352           if (extent < maxLength)
1353           {
1354             current.insertCharAt(extent, maxLength - extent, gapCharacter);
1355             modified = true;
1356           }
1357         }
1358       }
1359     }
1360     return modified;
1361   }
1362
1363   @Override
1364   public HiddenSequences getHiddenSequences()
1365   {
1366     return hiddenSequences;
1367   }
1368
1369   @Override
1370   public HiddenColumns getHiddenColumns()
1371   {
1372     return hiddenCols;
1373   }
1374
1375   @Override
1376   public CigarArray getCompactAlignment()
1377   {
1378     synchronized (sequences)
1379     {
1380       SeqCigar alseqs[] = new SeqCigar[sequences.size()];
1381       int i = 0;
1382       for (SequenceI seq : sequences)
1383       {
1384         alseqs[i++] = new SeqCigar(seq);
1385       }
1386       CigarArray cal = new CigarArray(alseqs);
1387       cal.addOperation(CigarArray.M, getWidth());
1388       return cal;
1389     }
1390   }
1391
1392   @Override
1393   public void setProperty(Object key, Object value)
1394   {
1395     if (alignmentProperties == null)
1396     {
1397       alignmentProperties = new Hashtable();
1398     }
1399
1400     alignmentProperties.put(key, value);
1401   }
1402
1403   @Override
1404   public Object getProperty(Object key)
1405   {
1406     if (alignmentProperties != null)
1407     {
1408       return alignmentProperties.get(key);
1409     }
1410     else
1411     {
1412       return null;
1413     }
1414   }
1415
1416   @Override
1417   public Hashtable getProperties()
1418   {
1419     return alignmentProperties;
1420   }
1421
1422   /**
1423    * Adds the given mapping to the stored set. Note this may be held on the
1424    * dataset alignment.
1425    */
1426   @Override
1427   public void addCodonFrame(AlignedCodonFrame codons)
1428   {
1429     List<AlignedCodonFrame> acfs = getCodonFrames();
1430     if (codons != null && acfs != null && !acfs.contains(codons))
1431     {
1432       acfs.add(codons);
1433     }
1434   }
1435
1436   /*
1437    * (non-Javadoc)
1438    * 
1439    * @see
1440    * jalview.datamodel.AlignmentI#getCodonFrame(jalview.datamodel.SequenceI)
1441    */
1442   @Override
1443   public List<AlignedCodonFrame> getCodonFrame(SequenceI seq)
1444   {
1445     if (seq == null)
1446     {
1447       return null;
1448     }
1449     List<AlignedCodonFrame> cframes = new ArrayList<>();
1450     for (AlignedCodonFrame acf : getCodonFrames())
1451     {
1452       if (acf.involvesSequence(seq))
1453       {
1454         cframes.add(acf);
1455       }
1456     }
1457     return cframes;
1458   }
1459
1460   /**
1461    * Sets the codon frame mappings (replacing any existing mappings). Note the
1462    * mappings are set on the dataset alignment instead if there is one.
1463    * 
1464    * @see jalview.datamodel.AlignmentI#setCodonFrames()
1465    */
1466   @Override
1467   public void setCodonFrames(List<AlignedCodonFrame> acfs)
1468   {
1469     if (dataset != null)
1470     {
1471       dataset.setCodonFrames(acfs);
1472     }
1473     else
1474     {
1475       this.codonFrameList = acfs;
1476     }
1477   }
1478
1479   /**
1480    * Returns the set of codon frame mappings. Any changes to the returned set
1481    * will affect the alignment. The mappings are held on (and read from) the
1482    * dataset alignment if there is one.
1483    * 
1484    * @see jalview.datamodel.AlignmentI#getCodonFrames()
1485    */
1486   @Override
1487   public List<AlignedCodonFrame> getCodonFrames()
1488   {
1489     // TODO: Fix this method to fix failing AlignedCodonFrame tests
1490     // this behaviour is currently incorrect. method should return codon frames
1491     // for just the alignment,
1492     // selected from dataset
1493     return dataset != null ? dataset.getCodonFrames() : codonFrameList;
1494   }
1495
1496   /**
1497    * Removes the given mapping from the stored set. Note that the mappings are
1498    * held on the dataset alignment if there is one.
1499    */
1500   @Override
1501   public boolean removeCodonFrame(AlignedCodonFrame codons)
1502   {
1503     List<AlignedCodonFrame> acfs = getCodonFrames();
1504     if (codons == null || acfs == null)
1505     {
1506       return false;
1507     }
1508     return acfs.remove(codons);
1509   }
1510
1511   @Override
1512   public void append(AlignmentI toappend)
1513   {
1514     // TODO JAL-1270 needs test coverage
1515     // currently tested for use in jalview.gui.SequenceFetcher
1516     char oldc = toappend.getGapCharacter();
1517     boolean samegap = oldc == getGapCharacter();
1518     boolean hashidden = toappend.getHiddenSequences() != null
1519             && toappend.getHiddenSequences().hiddenSequences != null;
1520     // get all sequences including any hidden ones
1521     List<SequenceI> sqs = (hashidden)
1522             ? toappend.getHiddenSequences().getFullAlignment()
1523                     .getSequences()
1524             : toappend.getSequences();
1525     if (sqs != null)
1526     {
1527       // avoid self append deadlock by
1528       List<SequenceI> toappendsq = new ArrayList<>();
1529       synchronized (sqs)
1530       {
1531         for (SequenceI addedsq : sqs)
1532         {
1533           if (!samegap)
1534           {
1535             addedsq.replace(oldc, gapCharacter);
1536           }
1537           toappendsq.add(addedsq);
1538         }
1539       }
1540       for (SequenceI addedsq : toappendsq)
1541       {
1542         addSequence(addedsq);
1543       }
1544     }
1545     AlignmentAnnotation[] alan = toappend.getAlignmentAnnotation();
1546     for (int a = 0; alan != null && a < alan.length; a++)
1547     {
1548       addAnnotation(alan[a]);
1549     }
1550
1551     // use add method
1552     getCodonFrames().addAll(toappend.getCodonFrames());
1553
1554     List<SequenceGroup> sg = toappend.getGroups();
1555     if (sg != null)
1556     {
1557       for (SequenceGroup _sg : sg)
1558       {
1559         addGroup(_sg);
1560       }
1561     }
1562     if (toappend.getHiddenSequences() != null)
1563     {
1564       HiddenSequences hs = toappend.getHiddenSequences();
1565       if (hiddenSequences == null)
1566       {
1567         hiddenSequences = new HiddenSequences(this);
1568       }
1569       if (hs.hiddenSequences != null)
1570       {
1571         for (int s = 0; s < hs.hiddenSequences.length; s++)
1572         {
1573           // hide the newly appended sequence in the alignment
1574           if (hs.hiddenSequences[s] != null)
1575           {
1576             hiddenSequences.hideSequence(hs.hiddenSequences[s]);
1577           }
1578         }
1579       }
1580     }
1581     if (toappend.getProperties() != null)
1582     {
1583       // we really can't do very much here - just try to concatenate strings
1584       // where property collisions occur.
1585       Enumeration key = toappend.getProperties().keys();
1586       while (key.hasMoreElements())
1587       {
1588         Object k = key.nextElement();
1589         Object ourval = this.getProperty(k);
1590         Object toapprop = toappend.getProperty(k);
1591         if (ourval != null)
1592         {
1593           if (ourval.getClass().equals(toapprop.getClass())
1594                   && !ourval.equals(toapprop))
1595           {
1596             if (ourval instanceof String)
1597             {
1598               // append strings
1599               this.setProperty(k,
1600                       ((String) ourval) + "; " + ((String) toapprop));
1601             }
1602             else
1603             {
1604               if (ourval instanceof Vector)
1605               {
1606                 // append vectors
1607                 Enumeration theirv = ((Vector) toapprop).elements();
1608                 while (theirv.hasMoreElements())
1609                 {
1610                   ((Vector) ourval).addElement(theirv);
1611                 }
1612               }
1613             }
1614           }
1615         }
1616         else
1617         {
1618           // just add new property directly
1619           setProperty(k, toapprop);
1620         }
1621
1622       }
1623     }
1624   }
1625
1626   @Override
1627   public AlignmentAnnotation findOrCreateAnnotation(String name,
1628           String calcId, boolean autoCalc, SequenceI seqRef,
1629           SequenceGroup groupRef)
1630   {
1631     if (annotations != null)
1632     {
1633       for (AlignmentAnnotation annot : getAlignmentAnnotation())
1634       {
1635         if (annot.autoCalculated == autoCalc && (name.equals(annot.label))
1636                 && (calcId == null || annot.getCalcId().equals(calcId))
1637                 && annot.sequenceRef == seqRef
1638                 && annot.groupRef == groupRef)
1639         {
1640           return annot;
1641         }
1642       }
1643     }
1644     AlignmentAnnotation annot = new AlignmentAnnotation(name, name,
1645             new Annotation[1], 0f, 0f, AlignmentAnnotation.BAR_GRAPH);
1646     annot.hasText = false;
1647     if (calcId != null)
1648     {
1649       annot.setCalcId(calcId);
1650     }
1651     annot.autoCalculated = autoCalc;
1652     if (seqRef != null)
1653     {
1654       annot.setSequenceRef(seqRef);
1655     }
1656     annot.groupRef = groupRef;
1657     addAnnotation(annot);
1658
1659     return annot;
1660   }
1661
1662   @Override
1663   public Iterable<AlignmentAnnotation> findAnnotation(String calcId)
1664   {
1665     AlignmentAnnotation[] alignmentAnnotation = getAlignmentAnnotation();
1666     if (alignmentAnnotation != null)
1667     {
1668       return AlignmentAnnotation.findAnnotation(
1669               Arrays.asList(getAlignmentAnnotation()), calcId);
1670     }
1671     return Arrays.asList(new AlignmentAnnotation[] {});
1672   }
1673
1674   @Override
1675   public Iterable<AlignmentAnnotation> findAnnotations(SequenceI seq,
1676           String calcId, String label)
1677   {
1678     return AlignmentAnnotation.findAnnotations(
1679             Arrays.asList(getAlignmentAnnotation()), seq, calcId, label);
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 boolean setHiddenColumns(HiddenColumns cols)
1939   {
1940     boolean changed = cols == null ? hiddenCols != null
1941             : !cols.equals(hiddenCols);
1942     hiddenCols = cols;
1943     return changed;
1944   }
1945
1946   @Override
1947   public void setupJPredAlignment()
1948   {
1949     SequenceI repseq = getSequenceAt(0);
1950     setSeqrep(repseq);
1951     HiddenColumns cs = new HiddenColumns();
1952     cs.hideList(repseq.getInsertions());
1953     setHiddenColumns(cs);
1954   }
1955
1956   @Override
1957   public HiddenColumns propagateInsertions(SequenceI profileseq,
1958           AlignmentView input)
1959   {
1960     int profsqpos = 0;
1961
1962     char gc = getGapCharacter();
1963     Object[] alandhidden = input.getAlignmentAndHiddenColumns(gc);
1964     HiddenColumns nview = (HiddenColumns) alandhidden[1];
1965     SequenceI origseq = ((SequenceI[]) alandhidden[0])[profsqpos];
1966     return propagateInsertions(profileseq, origseq, nview);
1967   }
1968
1969   /**
1970    * 
1971    * @param profileseq
1972    *          sequence in al which corresponds to origseq
1973    * @param al
1974    *          alignment which is to have gaps inserted into it
1975    * @param origseq
1976    *          sequence corresponding to profileseq which defines gap map for
1977    *          modifying al
1978    */
1979   private HiddenColumns propagateInsertions(SequenceI profileseq,
1980           SequenceI origseq, HiddenColumns hc)
1981   {
1982     // take the set of hidden columns, and the set of gaps in origseq,
1983     // and remove all the hidden gaps from hiddenColumns
1984
1985     // first get the gaps as a Bitset
1986     // then calculate hidden ^ not(gap)
1987     BitSet gaps = origseq.gapBitset();
1988     hc.andNot(gaps);
1989
1990     // for each sequence in the alignment, except the profile sequence,
1991     // insert gaps corresponding to each hidden region but where each hidden
1992     // column region is shifted backwards by the number of preceding visible
1993     // gaps update hidden columns at the same time
1994     HiddenColumns newhidden = new HiddenColumns();
1995
1996     int numGapsBefore = 0;
1997     int gapPosition = 0;
1998     Iterator<int[]> it = hc.iterator();
1999     while (it.hasNext())
2000     {
2001       int[] region = it.next();
2002
2003       // get region coordinates accounting for gaps
2004       // we can rely on gaps not being *in* hidden regions because we already
2005       // removed those
2006       while (gapPosition < region[0])
2007       {
2008         gapPosition++;
2009         if (gaps.get(gapPosition))
2010         {
2011           numGapsBefore++;
2012         }
2013       }
2014
2015       int left = region[0] - numGapsBefore;
2016       int right = region[1] - numGapsBefore;
2017
2018       newhidden.hideColumns(left, right);
2019       padGaps(left, right, profileseq);
2020     }
2021     return newhidden;
2022   }
2023
2024   /**
2025    * Pad gaps in all sequences in alignment except profileseq
2026    * 
2027    * @param left
2028    *          position of first gap to insert
2029    * @param right
2030    *          position of last gap to insert
2031    * @param profileseq
2032    *          sequence not to pad
2033    */
2034   private void padGaps(int left, int right, SequenceI profileseq)
2035   {
2036     char gc = getGapCharacter();
2037
2038     // make a string with number of gaps = length of hidden region
2039     StringBuilder sb = new StringBuilder();
2040     for (int g = 0; g < right - left + 1; g++)
2041     {
2042       sb.append(gc);
2043     }
2044
2045     // loop over the sequences and pad with gaps where required
2046     for (int s = 0, ns = getHeight(); s < ns; s++)
2047     {
2048       SequenceI sqobj = getSequenceAt(s);
2049       if ((sqobj != profileseq) && (sqobj.getLength() >= left))
2050       {
2051         String sq = sqobj.getSequenceAsString();
2052         sqobj.setSequence(
2053                 sq.substring(0, left) + sb.toString() + sq.substring(left));
2054       }
2055     }
2056   }
2057
2058   @Override
2059   public List<SequenceI> getHmmSequences()
2060   {
2061     List<SequenceI> result = new ArrayList<>();
2062     for (int i = 0; i < sequences.size(); i++)
2063     {
2064       SequenceI seq = sequences.get(i);
2065       if (seq.hasHMMProfile())
2066       {
2067         result.add(seq);
2068       }
2069     }
2070     return result;
2071   }
2072 }