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