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