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