JAL-3417 don't copy features when anonymising a non-dataset sequence
[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.analysis.Conservation;
25 import jalview.datamodel.AlignedCodonFrame.SequenceToSequenceMapping;
26 import jalview.io.FastaFile;
27 import jalview.util.Comparison;
28 import jalview.util.LinkedIdentityHashSet;
29 import jalview.util.MessageManager;
30
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.BitSet;
34 import java.util.Collections;
35 import java.util.Enumeration;
36 import java.util.HashSet;
37 import java.util.Hashtable;
38 import java.util.Iterator;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Set;
42 import java.util.Vector;
43
44 /**
45  * Data structure to hold and manipulate a multiple sequence alignment
46  */
47 /**
48  * @author JimP
49  * 
50  */
51 public class Alignment implements AlignmentI, AutoCloseable
52 {
53   private Alignment dataset;
54
55   private List<SequenceI> sequences;
56
57   protected List<SequenceGroup> groups;
58
59   protected char gapCharacter = '-';
60
61   private boolean nucleotide = true;
62
63   public boolean hasRNAStructure = false;
64
65   public AlignmentAnnotation[] annotations;
66
67   HiddenSequences hiddenSequences;
68
69   HiddenColumns hiddenCols;
70
71   public Hashtable alignmentProperties;
72
73   private List<AlignedCodonFrame> codonFrameList;
74
75   private Conservation conservation;
76
77   private ProfilesI consensus;
78
79   private Hashtable[] codonConsensus, rnaStructureConsensus;
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 seqs
136    *          SeqCigar[]
137    */
138   public Alignment(SeqCigar[] alseqs)
139   {
140     SequenceI[] seqs = SeqCigar.createAlignmentSequences(alseqs,
141             gapCharacter, new HiddenColumns(), null);
142     initAlignment(seqs);
143   }
144
145   /**
146    * Make a new alignment from an CigarArray JBPNote - can only do this when
147    * compactAlignment does not contain hidden regions. JBPNote - must also check
148    * that compactAlignment resolves to a set of SeqCigars - or construct them
149    * appropriately.
150    * 
151    * @param compactAlignment
152    *          CigarArray
153    */
154   public static AlignmentI createAlignment(CigarArray compactAlignment)
155   {
156     throw new Error(MessageManager
157             .getString("error.alignment_cigararray_not_implemented"));
158     // this(compactAlignment.refCigars);
159   }
160
161   @Override
162   public List<SequenceI> getSequences()
163   {
164     return sequences;
165   }
166
167   @Override
168   public List<SequenceI> getSequences(
169           Map<SequenceI, SequenceCollectionI> hiddenReps)
170   {
171     // TODO: in jalview 2.8 we don't do anything with hiddenreps - fix design to
172     // work on this.
173     return sequences;
174   }
175
176   @Override
177   public SequenceI[] getSequencesArray()
178   {
179     if (sequences == null)
180     {
181       return null;
182     }
183     synchronized (sequences)
184     {
185       return sequences.toArray(new SequenceI[sequences.size()]);
186     }
187   }
188
189   /**
190    * Returns a map of lists of sequences keyed by sequence name.
191    * 
192    * @return
193    */
194   @Override
195   public Map<String, List<SequenceI>> getSequencesByName()
196   {
197     return AlignmentUtils.getSequencesByName(this);
198   }
199
200   @Override
201   public SequenceI getSequenceAt(int i)
202   {
203     synchronized (sequences)
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 close()
313   {
314     if (getDataset() != null)
315     {
316       try
317       {
318         getDataset().removeAlignmentRef();
319       } catch (Throwable e)
320       {
321         e.printStackTrace();
322       }
323     }
324
325     nullReferences();
326   }
327
328   /**
329    * Defensively nulls out references in case this object is not garbage
330    * collected
331    */
332   void nullReferences()
333   {
334     dataset = null;
335     sequences = null;
336     groups = null;
337     annotations = null;
338     hiddenSequences = null;
339   }
340
341   /**
342    * decrement the alignmentRefs counter by one and null references if it goes
343    * to zero.
344    * 
345    * @throws Throwable
346    */
347   private void removeAlignmentRef() throws Throwable
348   {
349     if (--alignmentRefs == 0)
350     {
351       nullReferences();
352     }
353   }
354
355   @Override
356   public void deleteSequence(SequenceI s)
357   {
358     synchronized (sequences)
359     {
360       deleteSequence(findIndex(s));
361     }
362   }
363
364   @Override
365   public void deleteSequence(int i)
366   {
367     synchronized (sequences)
368     {
369       if (i > -1 && i < getHeight())
370       {
371         sequences.remove(i);
372         hiddenSequences.adjustHeightSequenceDeleted(i);
373       }
374     }
375   }
376
377   @Override
378   public void deleteHiddenSequence(int i)
379   {
380     synchronized (sequences)
381     {
382       if (i > -1 && i < getHeight())
383       {
384         sequences.remove(i);
385       }
386     }
387   }
388
389   /*
390    * (non-Javadoc)
391    * 
392    * @see jalview.datamodel.AlignmentI#findGroup(jalview.datamodel.SequenceI)
393    */
394   @Override
395   public SequenceGroup findGroup(SequenceI seq, int position)
396   {
397     synchronized (groups)
398     {
399       for (SequenceGroup sg : groups)
400       {
401         if (sg.getSequences(null).contains(seq))
402         {
403           if (position >= sg.getStartRes() && position <= sg.getEndRes())
404           {
405             return sg;
406           }
407         }
408       }
409     }
410     return null;
411   }
412
413   /*
414    * (non-Javadoc)
415    * 
416    * @see
417    * jalview.datamodel.AlignmentI#findAllGroups(jalview.datamodel.SequenceI)
418    */
419   @Override
420   public SequenceGroup[] findAllGroups(SequenceI s)
421   {
422     ArrayList<SequenceGroup> temp = new ArrayList<>();
423
424     synchronized (groups)
425     {
426       int gSize = groups.size();
427       for (int i = 0; i < gSize; i++)
428       {
429         SequenceGroup sg = groups.get(i);
430         if (sg == null || sg.getSequences() == null)
431         {
432           this.deleteGroup(sg);
433           gSize--;
434           continue;
435         }
436
437         if (sg.getSequences().contains(s))
438         {
439           temp.add(sg);
440         }
441       }
442     }
443     SequenceGroup[] ret = new SequenceGroup[temp.size()];
444     return temp.toArray(ret);
445   }
446
447   /**    */
448   @Override
449   public void addGroup(SequenceGroup sg)
450   {
451     synchronized (groups)
452     {
453       if (!groups.contains(sg))
454       {
455         if (hiddenSequences.getSize() > 0)
456         {
457           int i, iSize = sg.getSize();
458           for (i = 0; i < iSize; i++)
459           {
460             if (!sequences.contains(sg.getSequenceAt(i)))
461             {
462               sg.deleteSequence(sg.getSequenceAt(i), false);
463               iSize--;
464               i--;
465             }
466           }
467
468           if (sg.getSize() < 1)
469           {
470             return;
471           }
472         }
473         sg.setContext(this, true);
474         groups.add(sg);
475       }
476     }
477   }
478
479   /**
480    * remove any annotation that references gp
481    * 
482    * @param gp
483    *          (if null, removes all group associated annotation)
484    */
485   private void removeAnnotationForGroup(SequenceGroup gp)
486   {
487     if (annotations == null || annotations.length == 0)
488     {
489       return;
490     }
491     // remove annotation very quickly
492     AlignmentAnnotation[] t,
493             todelete = new AlignmentAnnotation[annotations.length],
494             tokeep = new AlignmentAnnotation[annotations.length];
495     int i, p, k;
496     if (gp == null)
497     {
498       for (i = 0, p = 0, k = 0; i < annotations.length; i++)
499       {
500         if (annotations[i].groupRef != null)
501         {
502           todelete[p++] = annotations[i];
503         }
504         else
505         {
506           tokeep[k++] = annotations[i];
507         }
508       }
509     }
510     else
511     {
512       for (i = 0, p = 0, k = 0; i < annotations.length; i++)
513       {
514         if (annotations[i].groupRef == gp)
515         {
516           todelete[p++] = annotations[i];
517         }
518         else
519         {
520           tokeep[k++] = annotations[i];
521         }
522       }
523     }
524     if (p > 0)
525     {
526       // clear out the group associated annotation.
527       for (i = 0; i < p; i++)
528       {
529         unhookAnnotation(todelete[i]);
530         todelete[i] = null;
531       }
532       t = new AlignmentAnnotation[k];
533       for (i = 0; i < k; i++)
534       {
535         t[i] = tokeep[i];
536       }
537       annotations = t;
538     }
539   }
540
541   @Override
542   public void deleteAllGroups()
543   {
544     synchronized (groups)
545     {
546       if (annotations != null)
547       {
548         removeAnnotationForGroup(null);
549       }
550       for (SequenceGroup sg : groups)
551       {
552         sg.setContext(null, false);
553       }
554       groups.clear();
555     }
556   }
557
558   /**    */
559   @Override
560   public void deleteGroup(SequenceGroup g)
561   {
562     synchronized (groups)
563     {
564       if (groups.contains(g))
565       {
566         removeAnnotationForGroup(g);
567         groups.remove(g);
568         g.setContext(null, false);
569       }
570     }
571   }
572
573   /**    */
574   @Override
575   public SequenceI findName(String name)
576   {
577     return findName(name, false);
578   }
579
580   /*
581    * (non-Javadoc)
582    * 
583    * @see jalview.datamodel.AlignmentI#findName(java.lang.String, boolean)
584    */
585   @Override
586   public SequenceI findName(String token, boolean b)
587   {
588     return findName(null, token, b);
589   }
590
591   /*
592    * (non-Javadoc)
593    * 
594    * @see jalview.datamodel.AlignmentI#findName(SequenceI, java.lang.String,
595    * boolean)
596    */
597   @Override
598   public SequenceI findName(SequenceI startAfter, String token, boolean b)
599   {
600
601     int i = 0;
602     SequenceI sq = null;
603     String sqname = null;
604     if (startAfter != null)
605     {
606       // try to find the sequence in the alignment
607       boolean matched = false;
608       while (i < sequences.size())
609       {
610         if (getSequenceAt(i++) == startAfter)
611         {
612           matched = true;
613           break;
614         }
615       }
616       if (!matched)
617       {
618         i = 0;
619       }
620     }
621     while (i < sequences.size())
622     {
623       sq = getSequenceAt(i);
624       sqname = sq.getName();
625       if (sqname.equals(token) // exact match
626               || (b && // allow imperfect matches - case varies
627                       (sqname.equalsIgnoreCase(token))))
628       {
629         return getSequenceAt(i);
630       }
631
632       i++;
633     }
634
635     return null;
636   }
637
638   @Override
639   public SequenceI[] findSequenceMatch(String name)
640   {
641     Vector matches = new Vector();
642     int i = 0;
643
644     while (i < sequences.size())
645     {
646       if (getSequenceAt(i).getName().equals(name))
647       {
648         matches.addElement(getSequenceAt(i));
649       }
650       i++;
651     }
652
653     SequenceI[] result = new SequenceI[matches.size()];
654     for (i = 0; i < result.length; i++)
655     {
656       result[i] = (SequenceI) matches.elementAt(i);
657     }
658
659     return result;
660
661   }
662
663   /*
664    * (non-Javadoc)
665    * 
666    * @see jalview.datamodel.AlignmentI#findIndex(jalview.datamodel.SequenceI)
667    */
668   @Override
669   public int findIndex(SequenceI s)
670   {
671     int i = 0;
672
673     while (i < sequences.size())
674     {
675       if (s == getSequenceAt(i))
676       {
677         return i;
678       }
679
680       i++;
681     }
682
683     return -1;
684   }
685
686   /*
687    * (non-Javadoc)
688    * 
689    * @see
690    * jalview.datamodel.AlignmentI#findIndex(jalview.datamodel.SearchResults)
691    */
692   @Override
693   public int findIndex(SearchResultsI results)
694   {
695     int i = 0;
696
697     while (i < sequences.size())
698     {
699       if (results.involvesSequence(getSequenceAt(i)))
700       {
701         return i;
702       }
703       i++;
704     }
705     return -1;
706   }
707
708   @Override
709   public int getHeight()
710   {
711     return sequences.size();
712   }
713
714   @Override
715   public int getAbsoluteHeight()
716   {
717     return sequences.size() + getHiddenSequences().getSize();
718   }
719
720   @Override
721   public int getWidth()
722   {
723     int maxLength = -1;
724   
725     for (int i = 0; i < sequences.size(); i++)
726     {
727       maxLength = Math.max(maxLength, getSequenceAt(i).getLength());
728     }
729     return maxLength;
730   }
731
732   @Override
733   public int getVisibleWidth()
734   {
735     int w = getWidth();
736     if (hiddenCols != null)
737     {
738       w -= hiddenCols.getSize();
739     }
740     return w;
741   }
742
743   /**
744    * DOCUMENT ME!
745    * 
746    * @param gc
747    *          DOCUMENT ME!
748    */
749   @Override
750   public void setGapCharacter(char gc)
751   {
752     gapCharacter = gc;
753     synchronized (sequences)
754     {
755       for (SequenceI seq : sequences)
756       {
757         seq.setSequence(seq.getSequenceAsString().replace('.', gc)
758                 .replace('-', gc).replace(' ', gc));
759       }
760     }
761   }
762
763   /**
764    * DOCUMENT ME!
765    * 
766    * @return DOCUMENT ME!
767    */
768   @Override
769   public char getGapCharacter()
770   {
771     return gapCharacter;
772   }
773
774   /*
775    * (non-Javadoc)
776    * 
777    * @see jalview.datamodel.AlignmentI#isAligned()
778    */
779   @Override
780   public boolean isAligned()
781   {
782     return isAligned(false);
783   }
784
785   /*
786    * (non-Javadoc)
787    * 
788    * @see jalview.datamodel.AlignmentI#isAligned(boolean)
789    */
790   @Override
791   public boolean isAligned(boolean includeHidden)
792   {
793     int width = getWidth();
794     if (hiddenSequences == null || hiddenSequences.getSize() == 0)
795     {
796       includeHidden = true; // no hidden sequences to check against.
797     }
798     for (int i = 0; i < sequences.size(); i++)
799     {
800       if (includeHidden || !hiddenSequences.isHidden(getSequenceAt(i)))
801       {
802         if (getSequenceAt(i).getLength() != width)
803         {
804           return false;
805         }
806       }
807     }
808
809     return true;
810   }
811
812   @Override
813   public boolean isHidden(int alignmentIndex)
814   {
815     return (getHiddenSequences().getHiddenSequence(alignmentIndex) != null);
816   }
817
818   /**
819    * Delete all annotations, including auto-calculated if the flag is set true.
820    * Returns true if at least one annotation was deleted, else false.
821    * 
822    * @param includingAutoCalculated
823    * @return
824    */
825   @Override
826   public boolean deleteAllAnnotations(boolean includingAutoCalculated)
827   {
828     boolean result = false;
829     for (AlignmentAnnotation alan : getAlignmentAnnotation())
830     {
831       if (!alan.autoCalculated || includingAutoCalculated)
832       {
833         deleteAnnotation(alan);
834         result = true;
835       }
836     }
837     return result;
838   }
839
840   /*
841    * (non-Javadoc)
842    * 
843    * @seejalview.datamodel.AlignmentI#deleteAnnotation(jalview.datamodel.
844    * AlignmentAnnotation)
845    */
846   @Override
847   public boolean deleteAnnotation(AlignmentAnnotation aa)
848   {
849     return deleteAnnotation(aa, true);
850   }
851
852   @Override
853   public boolean deleteAnnotation(AlignmentAnnotation aa, boolean unhook)
854   {
855     int aSize = 1;
856
857     if (annotations != null)
858     {
859       aSize = annotations.length;
860     }
861
862     if (aSize < 1)
863     {
864       return false;
865     }
866
867     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize - 1];
868
869     boolean swap = false;
870     int tIndex = 0;
871
872     for (int i = 0; i < aSize; i++)
873     {
874       if (annotations[i] == aa)
875       {
876         swap = true;
877         continue;
878       }
879       if (tIndex < temp.length)
880       {
881         temp[tIndex++] = annotations[i];
882       }
883     }
884
885     if (swap)
886     {
887       annotations = temp;
888       if (unhook)
889       {
890         unhookAnnotation(aa);
891       }
892     }
893     return swap;
894   }
895
896   /**
897    * remove any object references associated with this annotation
898    * 
899    * @param aa
900    */
901   private void unhookAnnotation(AlignmentAnnotation aa)
902   {
903     if (aa.sequenceRef != null)
904     {
905       aa.sequenceRef.removeAlignmentAnnotation(aa);
906     }
907     if (aa.groupRef != null)
908     {
909       // probably need to do more here in the future (post 2.5.0)
910       aa.groupRef = null;
911     }
912   }
913
914   /*
915    * (non-Javadoc)
916    * 
917    * @seejalview.datamodel.AlignmentI#addAnnotation(jalview.datamodel.
918    * AlignmentAnnotation)
919    */
920   @Override
921   public void addAnnotation(AlignmentAnnotation aa)
922   {
923     addAnnotation(aa, -1);
924   }
925
926   /*
927    * (non-Javadoc)
928    * 
929    * @seejalview.datamodel.AlignmentI#addAnnotation(jalview.datamodel.
930    * AlignmentAnnotation, int)
931    */
932   @Override
933   public void addAnnotation(AlignmentAnnotation aa, int pos)
934   {
935     if (aa.getRNAStruc() != null)
936     {
937       hasRNAStructure = true;
938     }
939
940     int aSize = 1;
941     if (annotations != null)
942     {
943       aSize = annotations.length + 1;
944     }
945
946     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize];
947     int i = 0;
948     if (pos == -1 || pos >= aSize)
949     {
950       temp[aSize - 1] = aa;
951     }
952     else
953     {
954       temp[pos] = aa;
955     }
956     if (aSize > 1)
957     {
958       int p = 0;
959       for (i = 0; i < (aSize - 1); i++, p++)
960       {
961         if (p == pos)
962         {
963           p++;
964         }
965         if (p < temp.length)
966         {
967           temp[p] = annotations[i];
968         }
969       }
970     }
971
972     annotations = temp;
973   }
974
975   @Override
976   public void setAnnotationIndex(AlignmentAnnotation aa, int index)
977   {
978     if (aa == null || annotations == null || annotations.length - 1 < index)
979     {
980       return;
981     }
982
983     int aSize = annotations.length;
984     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize];
985
986     temp[index] = aa;
987
988     for (int i = 0; i < aSize; i++)
989     {
990       if (i == index)
991       {
992         continue;
993       }
994
995       if (i < index)
996       {
997         temp[i] = annotations[i];
998       }
999       else
1000       {
1001         temp[i] = annotations[i - 1];
1002       }
1003     }
1004
1005     annotations = temp;
1006   }
1007
1008   @Override
1009   /**
1010    * returns all annotation on the alignment
1011    */
1012   public AlignmentAnnotation[] getAlignmentAnnotation()
1013   {
1014     return annotations;
1015   }
1016
1017   @Override
1018   public boolean isNucleotide()
1019   {
1020     return nucleotide;
1021   }
1022
1023   @Override
1024   public boolean hasRNAStructure()
1025   {
1026     // TODO can it happen that structure is removed from alignment?
1027     return hasRNAStructure;
1028   }
1029
1030   @Override
1031   public void setDataset(AlignmentI data)
1032   {
1033     if (dataset == null && data == null)
1034     {
1035       createDatasetAlignment();
1036     }
1037     else if (dataset == null && data != null)
1038     {
1039       if (data == this)
1040       {
1041         throw new IllegalArgumentException("Circular dataset reference");
1042       }
1043       if (!(data instanceof Alignment))
1044       {
1045         throw new Error(
1046                 "Implementation Error: jalview.datamodel.Alignment does not yet support other implementations of AlignmentI as its dataset reference");
1047       }
1048       dataset = (Alignment) data;
1049       for (int i = 0; i < getHeight(); i++)
1050       {
1051         SequenceI currentSeq = getSequenceAt(i);
1052         SequenceI dsq = currentSeq.getDatasetSequence();
1053         if (dsq == null)
1054         {
1055           dsq = currentSeq.createDatasetSequence();
1056           dataset.addSequence(dsq);
1057         }
1058         else
1059         {
1060           while (dsq.getDatasetSequence() != null)
1061           {
1062             dsq = dsq.getDatasetSequence();
1063           }
1064           if (dataset.findIndex(dsq) == -1)
1065           {
1066             dataset.addSequence(dsq);
1067           }
1068         }
1069       }
1070     }
1071     dataset.addAlignmentRef();
1072   }
1073
1074   /**
1075    * add dataset sequences to seq for currentSeq and any sequences it references
1076    */
1077   private void resolveAndAddDatasetSeq(SequenceI currentSeq,
1078           Set<SequenceI> seqs, boolean createDatasetSequence)
1079   {
1080     SequenceI alignedSeq = currentSeq;
1081     if (currentSeq.getDatasetSequence() != null)
1082     {
1083       currentSeq = currentSeq.getDatasetSequence();
1084     }
1085     else
1086     {
1087       if (createDatasetSequence)
1088       {
1089         currentSeq = currentSeq.createDatasetSequence();
1090       }
1091     }
1092
1093     List<SequenceI> toProcess = new ArrayList<>();
1094     toProcess.add(currentSeq);
1095     while (toProcess.size() > 0)
1096     {
1097       // use a queue ?
1098       SequenceI curDs = toProcess.remove(0);
1099
1100       if (!seqs.add(curDs))
1101       {
1102         continue;
1103       }
1104       // iterate over database references, making sure we add forward referenced
1105       // sequences
1106       if (curDs.getDBRefs() != null)
1107       {
1108         for (DBRefEntry dbr : curDs.getDBRefs())
1109         {
1110           if (dbr.getMap() != null && dbr.getMap().getTo() != null)
1111           {
1112             if (dbr.getMap().getTo() == alignedSeq)
1113             {
1114               /*
1115                * update mapping to be to the newly created dataset sequence
1116                */
1117               dbr.getMap().setTo(currentSeq);
1118             }
1119             if (dbr.getMap().getTo().getDatasetSequence() != null)
1120             {
1121               throw new Error("Implementation error: Map.getTo() for dbref "
1122                       + dbr + " from " + curDs.getName()
1123                       + " is not a dataset sequence.");
1124             }
1125             // we recurse to add all forward references to dataset sequences via
1126             // DBRefs/etc
1127             toProcess.add(dbr.getMap().getTo());
1128           }
1129         }
1130       }
1131     }
1132   }
1133
1134   /**
1135    * Creates a new dataset for this alignment. Can only be done once - if
1136    * dataset is not null this will not be performed.
1137    */
1138   public void createDatasetAlignment()
1139   {
1140     if (dataset != null)
1141     {
1142       return;
1143     }
1144     // try to avoid using SequenceI.equals at this stage, it will be expensive
1145     Set<SequenceI> seqs = new LinkedIdentityHashSet<>();
1146
1147     for (int i = 0; i < getHeight(); i++)
1148     {
1149       SequenceI currentSeq = getSequenceAt(i);
1150       resolveAndAddDatasetSeq(currentSeq, seqs, true);
1151     }
1152
1153     // verify all mappings are in dataset
1154     for (AlignedCodonFrame cf : codonFrameList)
1155     {
1156       for (SequenceToSequenceMapping ssm : cf.getMappings())
1157       {
1158         if (!seqs.contains(ssm.getFromSeq()))
1159         {
1160           resolveAndAddDatasetSeq(ssm.getFromSeq(), seqs, false);
1161         }
1162         if (!seqs.contains(ssm.getMapping().getTo()))
1163         {
1164           resolveAndAddDatasetSeq(ssm.getMapping().getTo(), seqs, false);
1165         }
1166       }
1167     }
1168     // finally construct dataset
1169     dataset = new Alignment(seqs.toArray(new SequenceI[seqs.size()]));
1170     // move mappings to the dataset alignment
1171     dataset.codonFrameList = this.codonFrameList;
1172     this.codonFrameList = null;
1173   }
1174
1175   /**
1176    * reference count for number of alignments referencing this one.
1177    */
1178   int alignmentRefs = 0;
1179
1180   /**
1181    * increase reference count to this alignment.
1182    */
1183   private void addAlignmentRef()
1184   {
1185     alignmentRefs++;
1186   }
1187
1188   @Override
1189   public Alignment getDataset()
1190   {
1191     return dataset;
1192   }
1193
1194   @Override
1195   public boolean padGaps()
1196   {
1197     boolean modified = false;
1198
1199     // Remove excess gaps from the end of alignment
1200     int maxLength = -1;
1201
1202     SequenceI current;
1203     for (int i = 0; i < sequences.size(); 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 < sequences.size(); 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 Hashtable[] getComplementConsensusHash()
2041   {
2042     return codonConsensus;
2043   }
2044
2045   @Override
2046   public Conservation getConservation()
2047   {
2048     return conservation;
2049   }
2050
2051   @Override
2052   public Hashtable[] getRnaStructureConsensusHash()
2053   {
2054     return rnaStructureConsensus;
2055   }
2056
2057   @Override
2058   public ProfilesI getSequenceConsensusHash()
2059   {
2060     return consensus;
2061   }
2062
2063   @Override
2064   public void setComplementConsensusHash(Hashtable[] hconsensus)
2065   {
2066     codonConsensus = hconsensus;
2067
2068   }
2069
2070   @Override
2071   public void setConservation(Conservation cons)
2072   {
2073     conservation = cons;
2074
2075   }
2076
2077   @Override
2078   public void setRnaStructureConsensusHash(Hashtable[] hStrucConsensus)
2079   {
2080     rnaStructureConsensus = hStrucConsensus;
2081
2082   }
2083
2084   @Override
2085   public void setSequenceConsensusHash(ProfilesI hconsensus)
2086   {
2087     consensus = hconsensus;
2088
2089   }
2090 }