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