35efe4f06898013a0e0fe1bf688c83bfece1a03c
[jalview.git] / src / jalview / datamodel / Alignment.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.0b1)
3  * Copyright (C) 2014 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 of the License, or (at your option) any later version.
10  *  
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  * The Jalview Authors are detailed in the 'AUTHORS' file.
18  */
19 package jalview.datamodel;
20
21 import java.util.*;
22
23 /**
24  * Data structure to hold and manipulate a multiple sequence alignment
25  */
26 /**
27  * @author JimP
28  * 
29  */
30 public class Alignment implements AlignmentI
31 {
32   protected Alignment dataset;
33
34   protected List<SequenceI> sequences;
35
36   protected List<SequenceGroup> groups = java.util.Collections
37           .synchronizedList(new ArrayList<SequenceGroup>());
38
39   protected char gapCharacter = '-';
40
41   protected int type = NUCLEOTIDE;
42
43   public static final int PROTEIN = 0;
44
45   public static final int NUCLEOTIDE = 1;
46
47   public boolean hasRNAStructure = false;
48
49   /** DOCUMENT ME!! */
50   public AlignmentAnnotation[] annotations;
51
52   HiddenSequences hiddenSequences = new HiddenSequences(this);
53
54   public Hashtable alignmentProperties;
55
56   private void initAlignment(SequenceI[] seqs)
57   {
58     int i = 0;
59
60     if (jalview.util.Comparison.isNucleotide(seqs))
61     {
62       type = NUCLEOTIDE;
63     }
64     else
65     {
66       type = PROTEIN;
67     }
68
69     sequences = java.util.Collections
70             .synchronizedList(new ArrayList<SequenceI>());
71
72     for (i = 0; i < seqs.length; i++)
73     {
74       sequences.add(seqs[i]);
75     }
76
77   }
78
79   /**
80    * Make an alignment from an array of Sequences.
81    * 
82    * @param sequences
83    */
84   public Alignment(SequenceI[] seqs)
85   {
86     initAlignment(seqs);
87   }
88
89   /**
90    * Make a new alignment from an array of SeqCigars
91    * 
92    * @param seqs
93    *          SeqCigar[]
94    */
95   public Alignment(SeqCigar[] alseqs)
96   {
97     SequenceI[] seqs = SeqCigar.createAlignmentSequences(alseqs,
98             gapCharacter, new ColumnSelection(), null);
99     initAlignment(seqs);
100   }
101
102   /**
103    * Make a new alignment from an CigarArray JBPNote - can only do this when
104    * compactAlignment does not contain hidden regions. JBPNote - must also check
105    * that compactAlignment resolves to a set of SeqCigars - or construct them
106    * appropriately.
107    * 
108    * @param compactAlignment
109    *          CigarArray
110    */
111   public static AlignmentI createAlignment(CigarArray compactAlignment)
112   {
113     throw new Error("Alignment(CigarArray) not yet implemented");
114     // this(compactAlignment.refCigars);
115   }
116
117   /**
118    * DOCUMENT ME!
119    * 
120    * @return DOCUMENT ME!
121    */
122   @Override
123   public List<SequenceI> getSequences()
124   {
125     return sequences;
126   }
127
128   @Override
129   public List<SequenceI> getSequences(
130           Map<SequenceI, SequenceCollectionI> hiddenReps)
131   {
132     // TODO: in jalview 2.8 we don't do anything with hiddenreps - fix design to
133     // work on this.
134     return sequences;
135   }
136
137   @Override
138   public SequenceI[] getSequencesArray()
139   {
140     if (sequences == null)
141       return null;
142     synchronized (sequences)
143     {
144       return sequences.toArray(new SequenceI[sequences.size()]);
145     }
146   }
147
148   /**
149    * DOCUMENT ME!
150    * 
151    * @param i
152    *          DOCUMENT ME!
153    * 
154    * @return DOCUMENT ME!
155    */
156   @Override
157   public SequenceI getSequenceAt(int i)
158   {
159     synchronized (sequences)
160     {
161       if (i > -1 && i < sequences.size())
162       {
163         return sequences.get(i);
164       }
165     }
166     return null;
167   }
168
169   /**
170    * Adds a sequence to the alignment. Recalculates maxLength and size.
171    * 
172    * @param snew
173    */
174   @Override
175   public void addSequence(SequenceI snew)
176   {
177     if (dataset != null)
178     {
179       // maintain dataset integrity
180       if (snew.getDatasetSequence() != null)
181       {
182         getDataset().addSequence(snew.getDatasetSequence());
183       }
184       else
185       {
186         // derive new sequence
187         SequenceI adding = snew.deriveSequence();
188         getDataset().addSequence(adding.getDatasetSequence());
189         snew = adding;
190       }
191     }
192     if (sequences == null)
193     {
194       initAlignment(new SequenceI[]
195       { snew });
196     }
197     else
198     {
199       synchronized (sequences)
200       {
201         sequences.add(snew);
202       }
203     }
204     if (hiddenSequences != null)
205       hiddenSequences.adjustHeightSequenceAdded();
206   }
207
208   /**
209    * Adds a sequence to the alignment. Recalculates maxLength and size.
210    * 
211    * @param snew
212    */
213   @Override
214   public void setSequenceAt(int i, SequenceI snew)
215   {
216     SequenceI oldseq = getSequenceAt(i);
217     deleteSequence(i);
218     synchronized (sequences)
219     {
220       sequences.set(i, snew);
221     }
222   }
223
224   /**
225    * DOCUMENT ME!
226    * 
227    * @return DOCUMENT ME!
228    */
229   @Override
230   public List<SequenceGroup> getGroups()
231   {
232     return groups;
233   }
234
235   @Override
236   public void finalize()
237   {
238     if (getDataset() != null)
239       getDataset().removeAlignmentRef();
240
241     dataset = null;
242     sequences = null;
243     groups = null;
244     annotations = null;
245     hiddenSequences = null;
246   }
247
248   /**
249    * decrement the alignmentRefs counter by one and call finalize if it goes to
250    * zero.
251    */
252   private void removeAlignmentRef()
253   {
254     if (--alignmentRefs == 0)
255     {
256       finalize();
257     }
258   }
259
260   /**
261    * DOCUMENT ME!
262    * 
263    * @param s
264    *          DOCUMENT ME!
265    */
266   @Override
267   public void deleteSequence(SequenceI s)
268   {
269     deleteSequence(findIndex(s));
270   }
271
272   /**
273    * DOCUMENT ME!
274    * 
275    * @param i
276    *          DOCUMENT ME!
277    */
278   @Override
279   public void deleteSequence(int i)
280   {
281     if (i > -1 && i < getHeight())
282     {
283       synchronized (sequences)
284       {
285         sequences.remove(i);
286       }
287       hiddenSequences.adjustHeightSequenceDeleted(i);
288     }
289   }
290
291   /*
292    * (non-Javadoc)
293    * 
294    * @see jalview.datamodel.AlignmentI#findGroup(jalview.datamodel.SequenceI)
295    */
296   @Override
297   public SequenceGroup findGroup(SequenceI s)
298   {
299     synchronized (groups)
300     {
301       for (int i = 0; i < this.groups.size(); i++)
302       {
303         SequenceGroup sg = groups.get(i);
304
305         if (sg.getSequences(null).contains(s))
306         {
307           return sg;
308         }
309       }
310     }
311     return null;
312   }
313
314   /*
315    * (non-Javadoc)
316    * 
317    * @see
318    * jalview.datamodel.AlignmentI#findAllGroups(jalview.datamodel.SequenceI)
319    */
320   @Override
321   public SequenceGroup[] findAllGroups(SequenceI s)
322   {
323     ArrayList<SequenceGroup> temp = new ArrayList<SequenceGroup>();
324
325     synchronized (groups)
326     {
327       int gSize = groups.size();
328       for (int i = 0; i < gSize; i++)
329       {
330         SequenceGroup sg = groups.get(i);
331         if (sg == null || sg.getSequences(null) == null)
332         {
333           this.deleteGroup(sg);
334           gSize--;
335           continue;
336         }
337
338         if (sg.getSequences(null).contains(s))
339         {
340           temp.add(sg);
341         }
342       }
343     }
344     SequenceGroup[] ret = new SequenceGroup[temp.size()];
345     return temp.toArray(ret);
346   }
347
348   /**    */
349   @Override
350   public void addGroup(SequenceGroup sg)
351   {
352     synchronized (groups)
353     {
354       if (!groups.contains(sg))
355       {
356         if (hiddenSequences.getSize() > 0)
357         {
358           int i, iSize = sg.getSize();
359           for (i = 0; i < iSize; i++)
360           {
361             if (!sequences.contains(sg.getSequenceAt(i)))
362             {
363               sg.deleteSequence(sg.getSequenceAt(i), false);
364               iSize--;
365               i--;
366             }
367           }
368
369           if (sg.getSize() < 1)
370           {
371             return;
372           }
373         }
374         sg.setContext(this);
375         groups.add(sg);
376       }
377     }
378   }
379
380   /**
381    * remove any annotation that references gp
382    * 
383    * @param gp
384    *          (if null, removes all group associated annotation)
385    */
386   private void removeAnnotationForGroup(SequenceGroup gp)
387   {
388     if (annotations == null || annotations.length == 0)
389     {
390       return;
391     }
392     // remove annotation very quickly
393     AlignmentAnnotation[] t, todelete = new AlignmentAnnotation[annotations.length], tokeep = new AlignmentAnnotation[annotations.length];
394     int i, p, k;
395     if (gp == null)
396     {
397       for (i = 0, p = 0, k = 0; i < annotations.length; i++)
398       {
399         if (annotations[i].groupRef != null)
400         {
401           todelete[p++] = annotations[i];
402         }
403         else
404         {
405           tokeep[k++] = annotations[i];
406         }
407       }
408     }
409     else
410     {
411       for (i = 0, p = 0, k = 0; i < annotations.length; i++)
412       {
413         if (annotations[i].groupRef == gp)
414         {
415           todelete[p++] = annotations[i];
416         }
417         else
418         {
419           tokeep[k++] = annotations[i];
420         }
421       }
422     }
423     if (p > 0)
424     {
425       // clear out the group associated annotation.
426       for (i = 0; i < p; i++)
427       {
428         unhookAnnotation(todelete[i]);
429         todelete[i] = null;
430       }
431       t = new AlignmentAnnotation[k];
432       for (i = 0; i < k; i++)
433       {
434         t[i] = tokeep[i];
435       }
436       annotations = t;
437     }
438   }
439
440   @Override
441   public void deleteAllGroups()
442   {
443     synchronized (groups)
444     {
445       if (annotations != null)
446       {
447         removeAnnotationForGroup(null);
448       }
449       for (SequenceGroup sg:groups) {
450         sg.setContext(null);
451       }
452       groups.clear();
453     }
454   }
455
456   /**    */
457   @Override
458   public void deleteGroup(SequenceGroup g)
459   {
460     synchronized (groups)
461     {
462       if (groups.contains(g))
463       {
464         removeAnnotationForGroup(g);
465         groups.remove(g);
466         g.setContext(null);
467       }
468     }
469   }
470
471   /**    */
472   @Override
473   public SequenceI findName(String name)
474   {
475     return findName(name, false);
476   }
477
478   /*
479    * (non-Javadoc)
480    * 
481    * @see jalview.datamodel.AlignmentI#findName(java.lang.String, boolean)
482    */
483   @Override
484   public SequenceI findName(String token, boolean b)
485   {
486     return findName(null, token, b);
487   }
488
489   /*
490    * (non-Javadoc)
491    * 
492    * @see jalview.datamodel.AlignmentI#findName(SequenceI, java.lang.String,
493    * boolean)
494    */
495   @Override
496   public SequenceI findName(SequenceI startAfter, String token, boolean b)
497   {
498
499     int i = 0;
500     SequenceI sq = null;
501     String sqname = null;
502     if (startAfter != null)
503     {
504       // try to find the sequence in the alignment
505       boolean matched = false;
506       while (i < sequences.size())
507       {
508         if (getSequenceAt(i++) == startAfter)
509         {
510           matched = true;
511           break;
512         }
513       }
514       if (!matched)
515       {
516         i = 0;
517       }
518     }
519     while (i < sequences.size())
520     {
521       sq = getSequenceAt(i);
522       sqname = sq.getName();
523       if (sqname.equals(token) // exact match
524               || (b && // allow imperfect matches - case varies
525               (sqname.equalsIgnoreCase(token))))
526       {
527         return getSequenceAt(i);
528       }
529
530       i++;
531     }
532
533     return null;
534   }
535
536   @Override
537   public SequenceI[] findSequenceMatch(String name)
538   {
539     Vector matches = new Vector();
540     int i = 0;
541
542     while (i < sequences.size())
543     {
544       if (getSequenceAt(i).getName().equals(name))
545       {
546         matches.addElement(getSequenceAt(i));
547       }
548       i++;
549     }
550
551     SequenceI[] result = new SequenceI[matches.size()];
552     for (i = 0; i < result.length; i++)
553     {
554       result[i] = (SequenceI) matches.elementAt(i);
555     }
556
557     return result;
558
559   }
560
561   /*
562    * (non-Javadoc)
563    * 
564    * @see jalview.datamodel.AlignmentI#findIndex(jalview.datamodel.SequenceI)
565    */
566   @Override
567   public int findIndex(SequenceI s)
568   {
569     int i = 0;
570
571     while (i < sequences.size())
572     {
573       if (s == getSequenceAt(i))
574       {
575         return i;
576       }
577
578       i++;
579     }
580
581     return -1;
582   }
583
584   /*
585    * (non-Javadoc)
586    * 
587    * @see
588    * jalview.datamodel.AlignmentI#findIndex(jalview.datamodel.SearchResults)
589    */
590   @Override
591   public int findIndex(SearchResults results)
592   {
593     int i = 0;
594
595     while (i < sequences.size())
596     {
597       if (results.involvesSequence(getSequenceAt(i)))
598       {
599         return i;
600       }
601       i++;
602     }
603     return -1;
604   }
605
606   /**
607    * DOCUMENT ME!
608    * 
609    * @return DOCUMENT ME!
610    */
611   @Override
612   public int getHeight()
613   {
614     return sequences.size();
615   }
616
617   /**
618    * DOCUMENT ME!
619    * 
620    * @return DOCUMENT ME!
621    */
622   @Override
623   public int getWidth()
624   {
625     int maxLength = -1;
626
627     for (int i = 0; i < sequences.size(); i++)
628     {
629       if (getSequenceAt(i).getLength() > maxLength)
630       {
631         maxLength = getSequenceAt(i).getLength();
632       }
633     }
634
635     return maxLength;
636   }
637
638   /**
639    * DOCUMENT ME!
640    * 
641    * @param gc
642    *          DOCUMENT ME!
643    */
644   @Override
645   public void setGapCharacter(char gc)
646   {
647     gapCharacter = gc;
648     synchronized (sequences)
649     {
650       for (SequenceI seq : sequences)
651       {
652         seq.setSequence(seq.getSequenceAsString().replace('.', gc)
653                 .replace('-', gc).replace(' ', gc));
654       }
655     }
656   }
657
658   /**
659    * DOCUMENT ME!
660    * 
661    * @return DOCUMENT ME!
662    */
663   @Override
664   public char getGapCharacter()
665   {
666     return gapCharacter;
667   }
668
669   /*
670    * (non-Javadoc)
671    * 
672    * @see jalview.datamodel.AlignmentI#isAligned()
673    */
674   @Override
675   public boolean isAligned()
676   {
677     return isAligned(false);
678   }
679
680   /*
681    * (non-Javadoc)
682    * 
683    * @see jalview.datamodel.AlignmentI#isAligned(boolean)
684    */
685   @Override
686   public boolean isAligned(boolean includeHidden)
687   {
688     int width = getWidth();
689     if (hiddenSequences == null || hiddenSequences.getSize() == 0)
690     {
691       includeHidden = true; // no hidden sequences to check against.
692     }
693     for (int i = 0; i < sequences.size(); i++)
694     {
695       if (includeHidden || !hiddenSequences.isHidden(getSequenceAt(i)))
696       {
697         if (getSequenceAt(i).getLength() != width)
698         {
699           return false;
700         }
701       }
702     }
703
704     return true;
705   }
706
707   /*
708    * (non-Javadoc)
709    * 
710    * @seejalview.datamodel.AlignmentI#deleteAnnotation(jalview.datamodel.
711    * AlignmentAnnotation)
712    */
713   @Override
714   public boolean deleteAnnotation(AlignmentAnnotation aa)
715   {
716     return deleteAnnotation(aa, true);
717   }
718
719   @Override
720   public boolean deleteAnnotation(AlignmentAnnotation aa, boolean unhook)
721   {
722     int aSize = 1;
723
724     if (annotations != null)
725     {
726       aSize = annotations.length;
727     }
728
729     if (aSize < 1)
730     {
731       return false;
732     }
733
734     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize - 1];
735
736     boolean swap = false;
737     int tIndex = 0;
738
739     for (int i = 0; i < aSize; i++)
740     {
741       if (annotations[i] == aa)
742       {
743         swap = true;
744         continue;
745       }
746       if (tIndex < temp.length)
747         temp[tIndex++] = annotations[i];
748     }
749
750     if (swap)
751     {
752       annotations = temp;
753       if (unhook)
754       {
755         unhookAnnotation(aa);
756       }
757     }
758     return swap;
759   }
760
761   /**
762    * remove any object references associated with this annotation
763    * 
764    * @param aa
765    */
766   private void unhookAnnotation(AlignmentAnnotation aa)
767   {
768     if (aa.sequenceRef != null)
769     {
770       aa.sequenceRef.removeAlignmentAnnotation(aa);
771     }
772     if (aa.groupRef != null)
773     {
774       // probably need to do more here in the future (post 2.5.0)
775       aa.groupRef = null;
776     }
777   }
778
779   /*
780    * (non-Javadoc)
781    * 
782    * @seejalview.datamodel.AlignmentI#addAnnotation(jalview.datamodel.
783    * AlignmentAnnotation)
784    */
785   @Override
786   public void addAnnotation(AlignmentAnnotation aa)
787   {
788     addAnnotation(aa, -1);
789   }
790
791   /*
792    * (non-Javadoc)
793    * 
794    * @seejalview.datamodel.AlignmentI#addAnnotation(jalview.datamodel.
795    * AlignmentAnnotation, int)
796    */
797   @Override
798   public void addAnnotation(AlignmentAnnotation aa, int pos)
799   {
800     if (aa.getRNAStruc() != null)
801     {
802       hasRNAStructure = true;
803     }
804
805     int aSize = 1;
806     if (annotations != null)
807     {
808       aSize = annotations.length + 1;
809     }
810
811     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize];
812     int i = 0;
813     if (pos == -1 || pos >= aSize)
814     {
815       temp[aSize - 1] = aa;
816     }
817     else
818     {
819       temp[pos] = aa;
820     }
821     if (aSize > 1)
822     {
823       int p = 0;
824       for (i = 0; i < (aSize - 1); i++, p++)
825       {
826         if (p == pos)
827         {
828           p++;
829         }
830         if (p < temp.length)
831         {
832           temp[p] = annotations[i];
833         }
834       }
835     }
836
837     annotations = temp;
838   }
839
840   @Override
841   public void setAnnotationIndex(AlignmentAnnotation aa, int index)
842   {
843     if (aa == null || annotations == null || annotations.length - 1 < index)
844     {
845       return;
846     }
847
848     int aSize = annotations.length;
849     AlignmentAnnotation[] temp = new AlignmentAnnotation[aSize];
850
851     temp[index] = aa;
852
853     for (int i = 0; i < aSize; i++)
854     {
855       if (i == index)
856       {
857         continue;
858       }
859
860       if (i < index)
861       {
862         temp[i] = annotations[i];
863       }
864       else
865       {
866         temp[i] = annotations[i - 1];
867       }
868     }
869
870     annotations = temp;
871   }
872
873   @Override
874   /**
875    * returns all annotation on the alignment
876    */
877   public AlignmentAnnotation[] getAlignmentAnnotation()
878   {
879     return annotations;
880   }
881
882   @Override
883   public void setNucleotide(boolean b)
884   {
885     if (b)
886     {
887       type = NUCLEOTIDE;
888     }
889     else
890     {
891       type = PROTEIN;
892     }
893   }
894
895   @Override
896   public boolean isNucleotide()
897   {
898     if (type == NUCLEOTIDE)
899     {
900       return true;
901     }
902     else
903     {
904       return false;
905     }
906   }
907
908   @Override
909   public boolean hasRNAStructure()
910   {
911     // TODO can it happen that structure is removed from alignment?
912     return hasRNAStructure;
913   }
914
915   @Override
916   public void setDataset(Alignment data)
917   {
918     if (dataset == null && data == null)
919     {
920       // Create a new dataset for this alignment.
921       // Can only be done once, if dataset is not null
922       // This will not be performed
923       SequenceI[] seqs = new SequenceI[getHeight()];
924       SequenceI currentSeq;
925       for (int i = 0; i < getHeight(); i++)
926       {
927         currentSeq = getSequenceAt(i);
928         if (currentSeq.getDatasetSequence() != null)
929         {
930           seqs[i] = currentSeq.getDatasetSequence();
931         }
932         else
933         {
934           seqs[i] = currentSeq.createDatasetSequence();
935         }
936       }
937
938       dataset = new Alignment(seqs);
939     }
940     else if (dataset == null && data != null)
941     {
942       dataset = data;
943     }
944     dataset.addAlignmentRef();
945   }
946
947   /**
948    * reference count for number of alignments referencing this one.
949    */
950   int alignmentRefs = 0;
951
952   /**
953    * increase reference count to this alignment.
954    */
955   private void addAlignmentRef()
956   {
957     alignmentRefs++;
958   }
959
960   @Override
961   public Alignment getDataset()
962   {
963     return dataset;
964   }
965
966   @Override
967   public boolean padGaps()
968   {
969     boolean modified = false;
970
971     // Remove excess gaps from the end of alignment
972     int maxLength = -1;
973
974     SequenceI current;
975     for (int i = 0; i < sequences.size(); i++)
976     {
977       current = getSequenceAt(i);
978       for (int j = current.getLength(); j > maxLength; j--)
979       {
980         if (j > maxLength
981                 && !jalview.util.Comparison.isGap(current.getCharAt(j)))
982         {
983           maxLength = j;
984           break;
985         }
986       }
987     }
988
989     maxLength++;
990
991     int cLength;
992     for (int i = 0; i < sequences.size(); i++)
993     {
994       current = getSequenceAt(i);
995       cLength = current.getLength();
996
997       if (cLength < maxLength)
998       {
999         current.insertCharAt(cLength, maxLength - cLength, gapCharacter);
1000         modified = true;
1001       }
1002       else if (current.getLength() > maxLength)
1003       {
1004         current.deleteChars(maxLength, current.getLength());
1005       }
1006     }
1007     return modified;
1008   }
1009
1010   /**
1011    * Justify the sequences to the left or right by deleting and inserting gaps
1012    * before the initial residue or after the terminal residue
1013    * 
1014    * @param right
1015    *          true if alignment padded to right, false to justify to left
1016    * @return true if alignment was changed
1017    */
1018   @Override
1019   public boolean justify(boolean right)
1020   {
1021     boolean modified = false;
1022
1023     // Remove excess gaps from the end of alignment
1024     int maxLength = -1;
1025     int ends[] = new int[sequences.size() * 2];
1026     SequenceI current;
1027     for (int i = 0; i < sequences.size(); i++)
1028     {
1029       current = getSequenceAt(i);
1030       // This should really be a sequence method
1031       ends[i * 2] = current.findIndex(current.getStart());
1032       ends[i * 2 + 1] = current.findIndex(current.getStart()
1033               + current.getLength());
1034       boolean hitres = false;
1035       for (int j = 0, rs = 0, ssiz = current.getLength(); j < ssiz; j++)
1036       {
1037         if (!jalview.util.Comparison.isGap(current.getCharAt(j)))
1038         {
1039           if (!hitres)
1040           {
1041             ends[i * 2] = j;
1042             hitres = true;
1043           }
1044           else
1045           {
1046             ends[i * 2 + 1] = j;
1047             if (j - ends[i * 2] > maxLength)
1048             {
1049               maxLength = j - ends[i * 2];
1050             }
1051           }
1052         }
1053       }
1054     }
1055
1056     maxLength++;
1057     // now edit the flanking gaps to justify to either left or right
1058     int cLength, extent, diff;
1059     for (int i = 0; i < sequences.size(); i++)
1060     {
1061       current = getSequenceAt(i);
1062
1063       cLength = 1 + ends[i * 2 + 1] - ends[i * 2];
1064       diff = maxLength - cLength; // number of gaps to indent
1065       extent = current.getLength();
1066       if (right)
1067       {
1068         // right justify
1069         if (extent > ends[i * 2 + 1])
1070         {
1071           current.deleteChars(ends[i * 2 + 1] + 1, extent);
1072           modified = true;
1073         }
1074         if (ends[i * 2] > diff)
1075         {
1076           current.deleteChars(0, ends[i * 2] - diff);
1077           modified = true;
1078         }
1079         else
1080         {
1081           if (ends[i * 2] < diff)
1082           {
1083             current.insertCharAt(0, diff - ends[i * 2], gapCharacter);
1084             modified = true;
1085           }
1086         }
1087       }
1088       else
1089       {
1090         // left justify
1091         if (ends[i * 2] > 0)
1092         {
1093           current.deleteChars(0, ends[i * 2]);
1094           modified = true;
1095           ends[i * 2 + 1] -= ends[i * 2];
1096           extent -= ends[i * 2];
1097         }
1098         if (extent > maxLength)
1099         {
1100           current.deleteChars(maxLength + 1, extent);
1101           modified = true;
1102         }
1103         else
1104         {
1105           if (extent < maxLength)
1106           {
1107             current.insertCharAt(extent, maxLength - extent, gapCharacter);
1108             modified = true;
1109           }
1110         }
1111       }
1112     }
1113     return modified;
1114   }
1115
1116   @Override
1117   public HiddenSequences getHiddenSequences()
1118   {
1119     return hiddenSequences;
1120   }
1121
1122   @Override
1123   public CigarArray getCompactAlignment()
1124   {
1125     synchronized (sequences)
1126     {
1127       SeqCigar alseqs[] = new SeqCigar[sequences.size()];
1128       int i = 0;
1129       for (SequenceI seq : sequences)
1130       {
1131         alseqs[i++] = new SeqCigar(seq);
1132       }
1133       CigarArray cal = new CigarArray(alseqs);
1134       cal.addOperation(CigarArray.M, getWidth());
1135       return cal;
1136     }
1137   }
1138
1139   @Override
1140   public void setProperty(Object key, Object value)
1141   {
1142     if (alignmentProperties == null)
1143       alignmentProperties = new Hashtable();
1144
1145     alignmentProperties.put(key, value);
1146   }
1147
1148   @Override
1149   public Object getProperty(Object key)
1150   {
1151     if (alignmentProperties != null)
1152       return alignmentProperties.get(key);
1153     else
1154       return null;
1155   }
1156
1157   @Override
1158   public Hashtable getProperties()
1159   {
1160     return alignmentProperties;
1161   }
1162
1163   AlignedCodonFrame[] codonFrameList = null;
1164
1165   /*
1166    * (non-Javadoc)
1167    * 
1168    * @see
1169    * jalview.datamodel.AlignmentI#addCodonFrame(jalview.datamodel.AlignedCodonFrame
1170    * )
1171    */
1172   @Override
1173   public void addCodonFrame(AlignedCodonFrame codons)
1174   {
1175     if (codons == null)
1176       return;
1177     if (codonFrameList == null)
1178     {
1179       codonFrameList = new AlignedCodonFrame[]
1180       { codons };
1181       return;
1182     }
1183     AlignedCodonFrame[] t = new AlignedCodonFrame[codonFrameList.length + 1];
1184     System.arraycopy(codonFrameList, 0, t, 0, codonFrameList.length);
1185     t[codonFrameList.length] = codons;
1186     codonFrameList = t;
1187   }
1188
1189   /*
1190    * (non-Javadoc)
1191    * 
1192    * @see jalview.datamodel.AlignmentI#getCodonFrame(int)
1193    */
1194   @Override
1195   public AlignedCodonFrame getCodonFrame(int index)
1196   {
1197     return codonFrameList[index];
1198   }
1199
1200   /*
1201    * (non-Javadoc)
1202    * 
1203    * @see
1204    * jalview.datamodel.AlignmentI#getCodonFrame(jalview.datamodel.SequenceI)
1205    */
1206   @Override
1207   public AlignedCodonFrame[] getCodonFrame(SequenceI seq)
1208   {
1209     if (seq == null || codonFrameList == null)
1210       return null;
1211     Vector cframes = new Vector();
1212     for (int f = 0; f < codonFrameList.length; f++)
1213     {
1214       if (codonFrameList[f].involvesSequence(seq))
1215         cframes.addElement(codonFrameList[f]);
1216     }
1217     if (cframes.size() == 0)
1218       return null;
1219     AlignedCodonFrame[] cfr = new AlignedCodonFrame[cframes.size()];
1220     cframes.copyInto(cfr);
1221     return cfr;
1222   }
1223
1224   /*
1225    * (non-Javadoc)
1226    * 
1227    * @see jalview.datamodel.AlignmentI#getCodonFrames()
1228    */
1229   @Override
1230   public AlignedCodonFrame[] getCodonFrames()
1231   {
1232     return codonFrameList;
1233   }
1234
1235   /*
1236    * (non-Javadoc)
1237    * 
1238    * @seejalview.datamodel.AlignmentI#removeCodonFrame(jalview.datamodel.
1239    * AlignedCodonFrame)
1240    */
1241   @Override
1242   public boolean removeCodonFrame(AlignedCodonFrame codons)
1243   {
1244     if (codons == null || codonFrameList == null)
1245       return false;
1246     boolean removed = false;
1247     int i = 0, iSize = codonFrameList.length;
1248     while (i < iSize)
1249     {
1250       if (codonFrameList[i] == codons)
1251       {
1252         removed = true;
1253         if (i + 1 < iSize)
1254         {
1255           System.arraycopy(codonFrameList, i + 1, codonFrameList, i, iSize
1256                   - i - 1);
1257         }
1258         iSize--;
1259       }
1260       else
1261       {
1262         i++;
1263       }
1264     }
1265     return removed;
1266   }
1267
1268   @Override
1269   public void append(AlignmentI toappend)
1270   {
1271     if (toappend == this)
1272     {
1273       System.err.println("Self append may cause a deadlock.");
1274     }
1275     // TODO test this method for a future 2.5 release
1276     // currently tested for use in jalview.gui.SequenceFetcher
1277     boolean samegap = toappend.getGapCharacter() == getGapCharacter();
1278     char oldc = toappend.getGapCharacter();
1279     boolean hashidden = toappend.getHiddenSequences() != null
1280             && toappend.getHiddenSequences().hiddenSequences != null;
1281     // get all sequences including any hidden ones
1282     List<SequenceI> sqs = (hashidden) ? toappend.getHiddenSequences()
1283             .getFullAlignment().getSequences() : toappend.getSequences();
1284     if (sqs != null)
1285     {
1286       synchronized (sqs)
1287       {
1288         for (SequenceI addedsq : sqs)
1289         {
1290           if (!samegap)
1291           {
1292             char[] oldseq = addedsq.getSequence();
1293             for (int c = 0; c < oldseq.length; c++)
1294             {
1295               if (oldseq[c] == oldc)
1296               {
1297                 oldseq[c] = gapCharacter;
1298               }
1299             }
1300           }
1301           addSequence(addedsq);
1302         }
1303       }
1304     }
1305     AlignmentAnnotation[] alan = toappend.getAlignmentAnnotation();
1306     for (int a = 0; alan != null && a < alan.length; a++)
1307     {
1308       addAnnotation(alan[a]);
1309     }
1310     AlignedCodonFrame[] acod = toappend.getCodonFrames();
1311     for (int a = 0; acod != null && a < acod.length; a++)
1312     {
1313       this.addCodonFrame(acod[a]);
1314     }
1315     List<SequenceGroup> sg = toappend.getGroups();
1316     if (sg != null)
1317     {
1318       for (SequenceGroup _sg : sg)
1319       {
1320         addGroup(_sg);
1321       }
1322     }
1323     if (toappend.getHiddenSequences() != null)
1324     {
1325       HiddenSequences hs = toappend.getHiddenSequences();
1326       if (hiddenSequences == null)
1327       {
1328         hiddenSequences = new HiddenSequences(this);
1329       }
1330       if (hs.hiddenSequences != null)
1331       {
1332         for (int s = 0; s < hs.hiddenSequences.length; s++)
1333         {
1334           // hide the newly appended sequence in the alignment
1335           if (hs.hiddenSequences[s] != null)
1336           {
1337             hiddenSequences.hideSequence(hs.hiddenSequences[s]);
1338           }
1339         }
1340       }
1341     }
1342     if (toappend.getProperties() != null)
1343     {
1344       // we really can't do very much here - just try to concatenate strings
1345       // where property collisions occur.
1346       Enumeration key = toappend.getProperties().keys();
1347       while (key.hasMoreElements())
1348       {
1349         Object k = key.nextElement();
1350         Object ourval = this.getProperty(k);
1351         Object toapprop = toappend.getProperty(k);
1352         if (ourval != null)
1353         {
1354           if (ourval.getClass().equals(toapprop.getClass())
1355                   && !ourval.equals(toapprop))
1356           {
1357             if (ourval instanceof String)
1358             {
1359               // append strings
1360               this.setProperty(k, ((String) ourval) + "; "
1361                       + ((String) toapprop));
1362             }
1363             else
1364             {
1365               if (ourval instanceof Vector)
1366               {
1367                 // append vectors
1368                 Enumeration theirv = ((Vector) toapprop).elements();
1369                 while (theirv.hasMoreElements())
1370                 {
1371                   ((Vector) ourval).addElement(theirv);
1372                 }
1373               }
1374             }
1375           }
1376         }
1377         else
1378         {
1379           // just add new property directly
1380           setProperty(k, toapprop);
1381         }
1382
1383       }
1384     }
1385   }
1386
1387   @Override
1388   public AlignmentAnnotation findOrCreateAnnotation(String name,
1389           String calcId, boolean autoCalc, SequenceI seqRef,
1390           SequenceGroup groupRef)
1391   {
1392     assert (name != null);
1393     if (annotations != null)
1394     {
1395       for (AlignmentAnnotation annot : getAlignmentAnnotation())
1396       {
1397         if (annot.autoCalculated == autoCalc && (name.equals(annot.label))
1398                 && (calcId == null || annot.getCalcId().equals(calcId))
1399                 && annot.sequenceRef == seqRef
1400                 && annot.groupRef == groupRef)
1401         {
1402           return annot;
1403         }
1404       }
1405     }
1406     AlignmentAnnotation annot = new AlignmentAnnotation(name, name,
1407             new Annotation[1], 0f, 0f, AlignmentAnnotation.BAR_GRAPH);
1408     annot.hasText = false;
1409     annot.setCalcId(new String(calcId));
1410     annot.autoCalculated = autoCalc;
1411     if (seqRef != null)
1412     {
1413       annot.setSequenceRef(seqRef);
1414     }
1415     annot.groupRef = groupRef;
1416     addAnnotation(annot);
1417
1418     return annot;
1419   }
1420
1421   @Override
1422   public Iterable<AlignmentAnnotation> findAnnotation(String calcId)
1423   {
1424     ArrayList<AlignmentAnnotation> aa = new ArrayList<AlignmentAnnotation>();
1425     for (AlignmentAnnotation a : getAlignmentAnnotation())
1426     {
1427       if (a.getCalcId() == calcId
1428               || (a.getCalcId() != null && calcId != null && a.getCalcId()
1429                       .equals(calcId)))
1430       {
1431         aa.add(a);
1432       }
1433     }
1434     return aa;
1435   }
1436
1437   @Override
1438   public void moveSelectedSequencesByOne(SequenceGroup sg,
1439           Map<SequenceI, SequenceCollectionI> map, boolean up)
1440   {
1441     synchronized (sequences)
1442     {
1443       if (up)
1444       {
1445
1446         for (int i = 1, iSize = sequences.size(); i < iSize; i++)
1447         {
1448           SequenceI seq = sequences.get(i);
1449           if (!sg.getSequences(map).contains(seq))
1450           {
1451             continue;
1452           }
1453
1454           SequenceI temp = sequences.get(i - 1);
1455           if (sg.getSequences(null).contains(temp))
1456           {
1457             continue;
1458           }
1459
1460           sequences.set(i, temp);
1461           sequences.set(i - 1, seq);
1462         }
1463       }
1464       else
1465       {
1466         for (int i = sequences.size() - 2; i > -1; i--)
1467         {
1468           SequenceI seq = sequences.get(i);
1469           if (!sg.getSequences(map).contains(seq))
1470           {
1471             continue;
1472           }
1473
1474           SequenceI temp = sequences.get(i + 1);
1475           if (sg.getSequences(map).contains(temp))
1476           {
1477             continue;
1478           }
1479
1480           sequences.set(i, temp);
1481           sequences.set(i + 1, seq);
1482         }
1483       }
1484
1485     }
1486   }
1487  @Override
1488  public void validateAnnotation(AlignmentAnnotation alignmentAnnotation)
1489  {
1490    alignmentAnnotation.validateRangeAndDisplay();
1491    if (isNucleotide() && alignmentAnnotation.isValidStruc())
1492    {
1493      hasRNAStructure = true;
1494    }
1495  }
1496  @Override
1497 public int getEndRes()
1498 {
1499   return getWidth()-1;
1500 }@Override
1501 public int getStartRes()
1502 {
1503   return 0;
1504 }
1505
1506 /* In the case of AlignmentI - returns the dataset for the alignment, if set
1507  * (non-Javadoc)
1508  * @see jalview.datamodel.AnnotatedCollectionI#getContext()
1509  */
1510 @Override
1511 public AnnotatedCollectionI getContext()
1512 {
1513   return dataset;
1514 }
1515 }