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