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