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