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