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