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