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