JAL-1834 removed quotes around truncated strings with ellipsis
[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.MessageManager;
26
27 import java.util.ArrayList;
28 import java.util.Enumeration;
29 import java.util.HashSet;
30 import java.util.Hashtable;
31 import java.util.LinkedHashSet;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Set;
35 import java.util.Vector;
36
37 /**
38  * Data structure to hold and manipulate a multiple sequence alignment
39  */
40 /**
41  * @author JimP
42  * 
43  */
44 public class Alignment implements AlignmentI
45 {
46   protected Alignment dataset;
47
48   protected List<SequenceI> sequences;
49
50   protected List<SequenceGroup> groups = java.util.Collections
51           .synchronizedList(new ArrayList<SequenceGroup>());
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   /** DOCUMENT ME!! */
64   public AlignmentAnnotation[] annotations;
65
66   HiddenSequences hiddenSequences = new HiddenSequences(this);
67
68   public Hashtable alignmentProperties;
69
70   private Set<AlignedCodonFrame> codonFrameList = new LinkedHashSet<AlignedCodonFrame>();
71
72   private void initAlignment(SequenceI[] seqs)
73   {
74     int i = 0;
75
76     if (jalview.util.Comparison.isNucleotide(seqs))
77     {
78       type = NUCLEOTIDE;
79     }
80     else
81     {
82       type = PROTEIN;
83     }
84
85     sequences = java.util.Collections
86             .synchronizedList(new ArrayList<SequenceI>());
87
88     for (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     /*
108      * Share the same dataset sequence mappings (if any). TODO: find a better
109      * place for these to live (alignment dataset?).
110      */
111     this.codonFrameList = ((Alignment) al).codonFrameList;
112
113     initAlignment(seqs);
114   }
115
116   /**
117    * Make an alignment from an array of Sequences.
118    * 
119    * @param sequences
120    */
121   public Alignment(SequenceI[] seqs)
122   {
123     initAlignment(seqs);
124   }
125
126   /**
127    * Make a new alignment from an array of SeqCigars
128    * 
129    * @param seqs
130    *          SeqCigar[]
131    */
132   public Alignment(SeqCigar[] alseqs)
133   {
134     SequenceI[] seqs = SeqCigar.createAlignmentSequences(alseqs,
135             gapCharacter, new ColumnSelection(), null);
136     initAlignment(seqs);
137   }
138
139   /**
140    * Make a new alignment from an CigarArray JBPNote - can only do this when
141    * compactAlignment does not contain hidden regions. JBPNote - must also check
142    * that compactAlignment resolves to a set of SeqCigars - or construct them
143    * appropriately.
144    * 
145    * @param compactAlignment
146    *          CigarArray
147    */
148   public static AlignmentI createAlignment(CigarArray compactAlignment)
149   {
150     throw new Error(MessageManager.getString("error.alignment_cigararray_not_implemented"));
151     // this(compactAlignment.refCigars);
152   }
153
154   @Override
155   public List<SequenceI> getSequences()
156   {
157     return sequences;
158   }
159
160   @Override
161   public List<SequenceI> getSequences(
162           Map<SequenceI, SequenceCollectionI> hiddenReps)
163   {
164     // TODO: in jalview 2.8 we don't do anything with hiddenreps - fix design to
165     // work on this.
166     return sequences;
167   }
168
169   @Override
170   public SequenceI[] getSequencesArray()
171   {
172     if (sequences == null)
173     {
174       return null;
175     }
176     synchronized (sequences)
177     {
178       return sequences.toArray(new SequenceI[sequences.size()]);
179     }
180   }
181
182   /**
183    * Returns a map of lists of sequences keyed by sequence name.
184    * 
185    * @return
186    */
187   @Override
188   public Map<String, List<SequenceI>> getSequencesByName()
189   {
190     return AlignmentUtils.getSequencesByName(this);
191   }
192
193   /**
194    * DOCUMENT ME!
195    * 
196    * @param i
197    *          DOCUMENT ME!
198    * 
199    * @return DOCUMENT ME!
200    */
201   @Override
202   public SequenceI getSequenceAt(int i)
203   {
204     synchronized (sequences)
205     {
206       if (i > -1 && i < sequences.size())
207       {
208         return sequences.get(i);
209       }
210     }
211     return null;
212   }
213
214   /**
215    * Adds a sequence to the alignment. Recalculates maxLength and size.
216    * 
217    * @param snew
218    */
219   @Override
220   public void addSequence(SequenceI snew)
221   {
222     if (dataset != null)
223     {
224       // maintain dataset integrity
225       if (snew.getDatasetSequence() != null)
226       {
227         getDataset().addSequence(snew.getDatasetSequence());
228       }
229       else
230       {
231         // derive new sequence
232         SequenceI adding = snew.deriveSequence();
233         getDataset().addSequence(adding.getDatasetSequence());
234         snew = adding;
235       }
236     }
237     if (sequences == null)
238     {
239       initAlignment(new SequenceI[]
240       { 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       // Create a new dataset for this alignment.
994       // Can only be done once, if dataset is not null
995       // This will not be performed
996       SequenceI[] seqs = new SequenceI[getHeight()];
997       SequenceI currentSeq;
998       for (int i = 0; i < getHeight(); i++)
999       {
1000         currentSeq = getSequenceAt(i);
1001         if (currentSeq.getDatasetSequence() != null)
1002         {
1003           seqs[i] = currentSeq.getDatasetSequence();
1004         }
1005         else
1006         {
1007           seqs[i] = currentSeq.createDatasetSequence();
1008         }
1009       }
1010
1011       dataset = new Alignment(seqs);
1012     }
1013     else if (dataset == null && data != null)
1014     {
1015       dataset = data;
1016       for (int i = 0; i < getHeight(); i++)
1017       {
1018         SequenceI currentSeq = getSequenceAt(i);
1019         SequenceI dsq = currentSeq.getDatasetSequence();
1020         if (dsq == null)
1021         {
1022           dsq = currentSeq.createDatasetSequence();
1023           dataset.addSequence(dsq);
1024         }
1025         else
1026         {
1027           while (dsq.getDatasetSequence() != null)
1028           {
1029             dsq = dsq.getDatasetSequence();
1030           }
1031           if (dataset.findIndex(dsq) == -1)
1032           {
1033             dataset.addSequence(dsq);
1034           }
1035         }
1036       }
1037     }
1038     dataset.addAlignmentRef();
1039   }
1040
1041   /**
1042    * reference count for number of alignments referencing this one.
1043    */
1044   int alignmentRefs = 0;
1045
1046   /**
1047    * increase reference count to this alignment.
1048    */
1049   private void addAlignmentRef()
1050   {
1051     alignmentRefs++;
1052   }
1053
1054   @Override
1055   public Alignment getDataset()
1056   {
1057     return dataset;
1058   }
1059
1060   @Override
1061   public boolean padGaps()
1062   {
1063     boolean modified = false;
1064
1065     // Remove excess gaps from the end of alignment
1066     int maxLength = -1;
1067
1068     SequenceI current;
1069     for (int i = 0; i < sequences.size(); i++)
1070     {
1071       current = getSequenceAt(i);
1072       for (int j = current.getLength(); j > maxLength; j--)
1073       {
1074         if (j > maxLength
1075                 && !jalview.util.Comparison.isGap(current.getCharAt(j)))
1076         {
1077           maxLength = j;
1078           break;
1079         }
1080       }
1081     }
1082
1083     maxLength++;
1084
1085     int cLength;
1086     for (int i = 0; i < sequences.size(); i++)
1087     {
1088       current = getSequenceAt(i);
1089       cLength = current.getLength();
1090
1091       if (cLength < maxLength)
1092       {
1093         current.insertCharAt(cLength, maxLength - cLength, gapCharacter);
1094         modified = true;
1095       }
1096       else if (current.getLength() > maxLength)
1097       {
1098         current.deleteChars(maxLength, current.getLength());
1099       }
1100     }
1101     return modified;
1102   }
1103
1104   /**
1105    * Justify the sequences to the left or right by deleting and inserting gaps
1106    * before the initial residue or after the terminal residue
1107    * 
1108    * @param right
1109    *          true if alignment padded to right, false to justify to left
1110    * @return true if alignment was changed
1111    */
1112   @Override
1113   public boolean justify(boolean right)
1114   {
1115     boolean modified = false;
1116
1117     // Remove excess gaps from the end of alignment
1118     int maxLength = -1;
1119     int ends[] = new int[sequences.size() * 2];
1120     SequenceI current;
1121     for (int i = 0; i < sequences.size(); i++)
1122     {
1123       current = getSequenceAt(i);
1124       // This should really be a sequence method
1125       ends[i * 2] = current.findIndex(current.getStart());
1126       ends[i * 2 + 1] = current.findIndex(current.getStart()
1127               + current.getLength());
1128       boolean hitres = false;
1129       for (int j = 0, rs = 0, ssiz = current.getLength(); j < ssiz; j++)
1130       {
1131         if (!jalview.util.Comparison.isGap(current.getCharAt(j)))
1132         {
1133           if (!hitres)
1134           {
1135             ends[i * 2] = j;
1136             hitres = true;
1137           }
1138           else
1139           {
1140             ends[i * 2 + 1] = j;
1141             if (j - ends[i * 2] > maxLength)
1142             {
1143               maxLength = j - ends[i * 2];
1144             }
1145           }
1146         }
1147       }
1148     }
1149
1150     maxLength++;
1151     // now edit the flanking gaps to justify to either left or right
1152     int cLength, extent, diff;
1153     for (int i = 0; i < sequences.size(); i++)
1154     {
1155       current = getSequenceAt(i);
1156
1157       cLength = 1 + ends[i * 2 + 1] - ends[i * 2];
1158       diff = maxLength - cLength; // number of gaps to indent
1159       extent = current.getLength();
1160       if (right)
1161       {
1162         // right justify
1163         if (extent > ends[i * 2 + 1])
1164         {
1165           current.deleteChars(ends[i * 2 + 1] + 1, extent);
1166           modified = true;
1167         }
1168         if (ends[i * 2] > diff)
1169         {
1170           current.deleteChars(0, ends[i * 2] - diff);
1171           modified = true;
1172         }
1173         else
1174         {
1175           if (ends[i * 2] < diff)
1176           {
1177             current.insertCharAt(0, diff - ends[i * 2], gapCharacter);
1178             modified = true;
1179           }
1180         }
1181       }
1182       else
1183       {
1184         // left justify
1185         if (ends[i * 2] > 0)
1186         {
1187           current.deleteChars(0, ends[i * 2]);
1188           modified = true;
1189           ends[i * 2 + 1] -= ends[i * 2];
1190           extent -= ends[i * 2];
1191         }
1192         if (extent > maxLength)
1193         {
1194           current.deleteChars(maxLength + 1, extent);
1195           modified = true;
1196         }
1197         else
1198         {
1199           if (extent < maxLength)
1200           {
1201             current.insertCharAt(extent, maxLength - extent, gapCharacter);
1202             modified = true;
1203           }
1204         }
1205       }
1206     }
1207     return modified;
1208   }
1209
1210   @Override
1211   public HiddenSequences getHiddenSequences()
1212   {
1213     return hiddenSequences;
1214   }
1215
1216   @Override
1217   public CigarArray getCompactAlignment()
1218   {
1219     synchronized (sequences)
1220     {
1221       SeqCigar alseqs[] = new SeqCigar[sequences.size()];
1222       int i = 0;
1223       for (SequenceI seq : sequences)
1224       {
1225         alseqs[i++] = new SeqCigar(seq);
1226       }
1227       CigarArray cal = new CigarArray(alseqs);
1228       cal.addOperation(CigarArray.M, getWidth());
1229       return cal;
1230     }
1231   }
1232
1233   @Override
1234   public void setProperty(Object key, Object value)
1235   {
1236     if (alignmentProperties == null)
1237     {
1238       alignmentProperties = new Hashtable();
1239     }
1240
1241     alignmentProperties.put(key, value);
1242   }
1243
1244   @Override
1245   public Object getProperty(Object key)
1246   {
1247     if (alignmentProperties != null)
1248     {
1249       return alignmentProperties.get(key);
1250     }
1251     else
1252     {
1253       return null;
1254     }
1255   }
1256
1257   @Override
1258   public Hashtable getProperties()
1259   {
1260     return alignmentProperties;
1261   }
1262
1263   /*
1264    * (non-Javadoc)
1265    * 
1266    * @see
1267    * jalview.datamodel.AlignmentI#addCodonFrame(jalview.datamodel.AlignedCodonFrame
1268    * )
1269    */
1270   @Override
1271   public void addCodonFrame(AlignedCodonFrame codons)
1272   {
1273     if (codons != null)
1274     {
1275       codonFrameList.add(codons);
1276     }
1277   }
1278
1279   /*
1280    * (non-Javadoc)
1281    * 
1282    * @see
1283    * jalview.datamodel.AlignmentI#getCodonFrame(jalview.datamodel.SequenceI)
1284    */
1285   @Override
1286   public List<AlignedCodonFrame> getCodonFrame(SequenceI seq)
1287   {
1288     if (seq == null)
1289     {
1290       return null;
1291     }
1292     List<AlignedCodonFrame> cframes = new ArrayList<AlignedCodonFrame>();
1293     for (AlignedCodonFrame acf : codonFrameList)
1294     {
1295       if (acf.involvesSequence(seq))
1296       {
1297         cframes.add(acf);
1298       }
1299     }
1300     return cframes;
1301   }
1302
1303   /**
1304    * Sets the codon frame mappings (replacing any existing mappings).
1305    * 
1306    * @see jalview.datamodel.AlignmentI#setCodonFrames()
1307    */
1308   @Override
1309   public void setCodonFrames(Set<AlignedCodonFrame> acfs)
1310   {
1311     this.codonFrameList = acfs;
1312   }
1313
1314   /**
1315    * Returns the set of codon frame mappings. Any changes to the returned set
1316    * will affect the alignment.
1317    * 
1318    * @see jalview.datamodel.AlignmentI#getCodonFrames()
1319    */
1320   @Override
1321   public Set<AlignedCodonFrame> getCodonFrames()
1322   {
1323     return codonFrameList;
1324   }
1325
1326   /*
1327    * (non-Javadoc)
1328    * 
1329    * @seejalview.datamodel.AlignmentI#removeCodonFrame(jalview.datamodel.
1330    * AlignedCodonFrame)
1331    */
1332   @Override
1333   public boolean removeCodonFrame(AlignedCodonFrame codons)
1334   {
1335     if (codons == null || codonFrameList == null)
1336     {
1337       return false;
1338     }
1339     return codonFrameList.remove(codons);
1340   }
1341
1342   @Override
1343   public void append(AlignmentI toappend)
1344   {
1345     if (toappend == this)
1346     {
1347       System.err.println("Self append may cause a deadlock.");
1348     }
1349     // TODO test this method for a future 2.5 release
1350     // currently tested for use in jalview.gui.SequenceFetcher
1351     boolean samegap = toappend.getGapCharacter() == getGapCharacter();
1352     char oldc = toappend.getGapCharacter();
1353     boolean hashidden = toappend.getHiddenSequences() != null
1354             && toappend.getHiddenSequences().hiddenSequences != null;
1355     // get all sequences including any hidden ones
1356     List<SequenceI> sqs = (hashidden) ? toappend.getHiddenSequences()
1357             .getFullAlignment().getSequences() : toappend.getSequences();
1358     if (sqs != null)
1359     {
1360       synchronized (sqs)
1361       {
1362         for (SequenceI addedsq : sqs)
1363         {
1364           if (!samegap)
1365           {
1366             char[] oldseq = addedsq.getSequence();
1367             for (int c = 0; c < oldseq.length; c++)
1368             {
1369               if (oldseq[c] == oldc)
1370               {
1371                 oldseq[c] = gapCharacter;
1372               }
1373             }
1374           }
1375           addSequence(addedsq);
1376         }
1377       }
1378     }
1379     AlignmentAnnotation[] alan = toappend.getAlignmentAnnotation();
1380     for (int a = 0; alan != null && a < alan.length; a++)
1381     {
1382       addAnnotation(alan[a]);
1383     }
1384
1385     this.codonFrameList.addAll(toappend.getCodonFrames());
1386
1387     List<SequenceGroup> sg = toappend.getGroups();
1388     if (sg != null)
1389     {
1390       for (SequenceGroup _sg : sg)
1391       {
1392         addGroup(_sg);
1393       }
1394     }
1395     if (toappend.getHiddenSequences() != null)
1396     {
1397       HiddenSequences hs = toappend.getHiddenSequences();
1398       if (hiddenSequences == null)
1399       {
1400         hiddenSequences = new HiddenSequences(this);
1401       }
1402       if (hs.hiddenSequences != null)
1403       {
1404         for (int s = 0; s < hs.hiddenSequences.length; s++)
1405         {
1406           // hide the newly appended sequence in the alignment
1407           if (hs.hiddenSequences[s] != null)
1408           {
1409             hiddenSequences.hideSequence(hs.hiddenSequences[s]);
1410           }
1411         }
1412       }
1413     }
1414     if (toappend.getProperties() != null)
1415     {
1416       // we really can't do very much here - just try to concatenate strings
1417       // where property collisions occur.
1418       Enumeration key = toappend.getProperties().keys();
1419       while (key.hasMoreElements())
1420       {
1421         Object k = key.nextElement();
1422         Object ourval = this.getProperty(k);
1423         Object toapprop = toappend.getProperty(k);
1424         if (ourval != null)
1425         {
1426           if (ourval.getClass().equals(toapprop.getClass())
1427                   && !ourval.equals(toapprop))
1428           {
1429             if (ourval instanceof String)
1430             {
1431               // append strings
1432               this.setProperty(k, ((String) ourval) + "; "
1433                       + ((String) toapprop));
1434             }
1435             else
1436             {
1437               if (ourval instanceof Vector)
1438               {
1439                 // append vectors
1440                 Enumeration theirv = ((Vector) toapprop).elements();
1441                 while (theirv.hasMoreElements())
1442                 {
1443                   ((Vector) ourval).addElement(theirv);
1444                 }
1445               }
1446             }
1447           }
1448         }
1449         else
1450         {
1451           // just add new property directly
1452           setProperty(k, toapprop);
1453         }
1454
1455       }
1456     }
1457   }
1458
1459   @Override
1460   public AlignmentAnnotation findOrCreateAnnotation(String name,
1461           String calcId, boolean autoCalc, SequenceI seqRef,
1462           SequenceGroup groupRef)
1463   {
1464     assert (name != null);
1465     if (annotations != null)
1466     {
1467       for (AlignmentAnnotation annot : getAlignmentAnnotation())
1468       {
1469         if (annot.autoCalculated == autoCalc && (name.equals(annot.label))
1470                 && (calcId == null || annot.getCalcId().equals(calcId))
1471                 && annot.sequenceRef == seqRef
1472                 && annot.groupRef == groupRef)
1473         {
1474           return annot;
1475         }
1476       }
1477     }
1478     AlignmentAnnotation annot = new AlignmentAnnotation(name, name,
1479             new Annotation[1], 0f, 0f, AlignmentAnnotation.BAR_GRAPH);
1480     annot.hasText = false;
1481     annot.setCalcId(new String(calcId));
1482     annot.autoCalculated = autoCalc;
1483     if (seqRef != null)
1484     {
1485       annot.setSequenceRef(seqRef);
1486     }
1487     annot.groupRef = groupRef;
1488     addAnnotation(annot);
1489
1490     return annot;
1491   }
1492
1493   @Override
1494   public Iterable<AlignmentAnnotation> findAnnotation(String calcId)
1495   {
1496     ArrayList<AlignmentAnnotation> aa = new ArrayList<AlignmentAnnotation>();
1497     for (AlignmentAnnotation a : getAlignmentAnnotation())
1498     {
1499       if (a.getCalcId() == calcId
1500               || (a.getCalcId() != null && calcId != null && a.getCalcId()
1501                       .equals(calcId)))
1502       {
1503         aa.add(a);
1504       }
1505     }
1506     return aa;
1507   }
1508
1509   /**
1510    * Returns an iterable collection of any annotations that match on given
1511    * sequence ref, calcId and label (ignoring null values).
1512    */
1513   @Override
1514   public Iterable<AlignmentAnnotation> findAnnotations(SequenceI seq,
1515           String calcId, String label)
1516   {
1517     ArrayList<AlignmentAnnotation> aa = new ArrayList<AlignmentAnnotation>();
1518     for (AlignmentAnnotation ann : getAlignmentAnnotation())
1519     {
1520       if (ann.getCalcId() != null && ann.getCalcId().equals(calcId)
1521               && ann.sequenceRef != null && ann.sequenceRef == seq
1522               && ann.label != null && ann.label.equals(label))
1523       {
1524         aa.add(ann);
1525       }
1526     }
1527     return aa;
1528   }
1529
1530   @Override
1531   public void moveSelectedSequencesByOne(SequenceGroup sg,
1532           Map<SequenceI, SequenceCollectionI> map, boolean up)
1533   {
1534     synchronized (sequences)
1535     {
1536       if (up)
1537       {
1538
1539         for (int i = 1, iSize = sequences.size(); i < iSize; i++)
1540         {
1541           SequenceI seq = sequences.get(i);
1542           if (!sg.getSequences(map).contains(seq))
1543           {
1544             continue;
1545           }
1546
1547           SequenceI temp = sequences.get(i - 1);
1548           if (sg.getSequences(null).contains(temp))
1549           {
1550             continue;
1551           }
1552
1553           sequences.set(i, temp);
1554           sequences.set(i - 1, seq);
1555         }
1556       }
1557       else
1558       {
1559         for (int i = sequences.size() - 2; i > -1; i--)
1560         {
1561           SequenceI seq = sequences.get(i);
1562           if (!sg.getSequences(map).contains(seq))
1563           {
1564             continue;
1565           }
1566
1567           SequenceI temp = sequences.get(i + 1);
1568           if (sg.getSequences(map).contains(temp))
1569           {
1570             continue;
1571           }
1572
1573           sequences.set(i, temp);
1574           sequences.set(i + 1, seq);
1575         }
1576       }
1577
1578     }
1579   }
1580
1581   @Override
1582   public void validateAnnotation(AlignmentAnnotation alignmentAnnotation)
1583   {
1584     alignmentAnnotation.validateRangeAndDisplay();
1585     if (isNucleotide() && alignmentAnnotation.isValidStruc())
1586     {
1587       hasRNAStructure = true;
1588     }
1589   }
1590
1591
1592  private SequenceI seqrep=null;
1593
1594  /**
1595   * 
1596   * @return the representative sequence for this group
1597   */
1598  public SequenceI getSeqrep()
1599  {
1600    return seqrep;
1601  }
1602
1603  /**
1604   * set the representative sequence for this group. Note - this affects the
1605   * interpretation of the Hidereps attribute.
1606   * 
1607   * @param seqrep
1608   *          the seqrep to set (null means no sequence representative)
1609   */
1610  public void setSeqrep(SequenceI seqrep)
1611  {
1612    this.seqrep = seqrep;
1613  }
1614
1615  /**
1616   * 
1617   * @return true if group has a sequence representative
1618   */
1619  public boolean hasSeqrep()
1620  {
1621    return seqrep != null;
1622  }
1623
1624   @Override
1625   public int getEndRes()
1626   {
1627     return getWidth() - 1;
1628   }
1629
1630   @Override
1631   public int getStartRes()
1632   {
1633     return 0;
1634   }
1635
1636   /*
1637    * In the case of AlignmentI - returns the dataset for the alignment, if set
1638    * (non-Javadoc)
1639    * 
1640    * @see jalview.datamodel.AnnotatedCollectionI#getContext()
1641    */
1642   @Override
1643   public AnnotatedCollectionI getContext()
1644   {
1645     return dataset;
1646   }
1647
1648   /**
1649    * Align this alignment like the given (mapped) one.
1650    */
1651   @Override
1652   public int alignAs(AlignmentI al)
1653   {
1654     /*
1655      * Currently retains unmapped gaps (in introns), regaps mapped regions
1656      * (exons)
1657      */
1658     return alignAs(al, false, true);
1659   }
1660
1661   /**
1662    * Align this alignment 'the same as' the given one. Mapped sequences only are
1663    * realigned. If both of the same type (nucleotide/protein) then align both
1664    * identically. If this is nucleotide and the other is protein, make 3 gaps
1665    * for each gap in the protein sequences. If this is protein and the other is
1666    * nucleotide, insert a gap for each 3 gaps (or part thereof) between
1667    * nucleotide bases. If this is protein and the other is nucleotide, gaps
1668    * protein to match the relative ordering of codons in the nucleotide.
1669    * 
1670    * Parameters control whether gaps in exon (mapped) and intron (unmapped)
1671    * regions are preserved. Gaps that connect introns to exons are treated
1672    * conservatively, i.e. only preserved if both intron and exon gaps are
1673    * preserved.
1674    * 
1675    * @param al
1676    * @param preserveMappedGaps
1677    *          if true, gaps within and between mapped codons are preserved
1678    * @param preserveUnmappedGaps
1679    *          if true, gaps within and between unmapped codons are preserved
1680    */
1681 //  @Override
1682   public int alignAs(AlignmentI al, boolean preserveMappedGaps,
1683           boolean preserveUnmappedGaps)
1684   {
1685     // TODO should this method signature be the one in the interface?
1686     int count = 0;
1687     boolean thisIsNucleotide = this.isNucleotide();
1688     boolean thatIsProtein = !al.isNucleotide();
1689     if (!thatIsProtein && !thisIsNucleotide)
1690     {
1691       return AlignmentUtils.alignProteinAsDna(this, al);
1692     }
1693
1694     char thisGapChar = this.getGapCharacter();
1695     String gap = thisIsNucleotide && thatIsProtein ? String
1696             .valueOf(new char[]
1697             { thisGapChar, thisGapChar, thisGapChar }) : String
1698             .valueOf(thisGapChar);
1699
1700     // TODO handle intron regions? Needs a 'holistic' alignment of dna,
1701     // not just sequence by sequence. But how to 'gap' intron regions?
1702
1703     /*
1704      * Get mappings from 'that' alignment's sequences to this.
1705      */
1706     for (SequenceI alignTo : getSequences())
1707     {
1708       count += AlignmentUtils.alignSequenceAs(alignTo, al, gap, preserveMappedGaps,
1709               preserveUnmappedGaps) ? 1 : 0;
1710     }
1711     return count;
1712   }
1713
1714   /**
1715    * Returns the alignment in Fasta format. Behaviour of this method is not
1716    * guaranteed between versions.
1717    */
1718   @Override
1719   public String toString()
1720   {
1721     return new FastaFile().print(getSequencesArray());
1722   }
1723
1724   /**
1725    * Returns the set of distinct sequence names. No ordering is guaranteed.
1726    */
1727   @Override
1728   public Set<String> getSequenceNames()
1729   {
1730     Set<String> names = new HashSet<String>();
1731     for (SequenceI seq : getSequences())
1732     {
1733       names.add(seq.getName());
1734     }
1735     return names;
1736   }
1737
1738   @Override
1739   public boolean hasValidSequence()
1740   {
1741     boolean hasValidSeq = false;
1742     for (SequenceI seq : getSequences())
1743     {
1744       if ((seq.getEnd() - seq.getStart()) > 0)
1745       {
1746         hasValidSeq = true;
1747         break;
1748       }
1749     }
1750     return hasValidSeq;
1751   }
1752 }