Merge branch 'merge/JAL-3285_mchmmer_with_211_develop' into alpha/JAL-3362_Jalview_21...
[jalview.git] / src / jalview / datamodel / Sequence.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.datamodel;
22
23 import jalview.analysis.AlignSeq;
24 import jalview.api.DBRefEntryI;
25 import jalview.datamodel.features.SequenceFeatures;
26 import jalview.datamodel.features.SequenceFeaturesI;
27 import jalview.util.Comparison;
28 import jalview.util.DBRefUtils;
29 import jalview.util.MapList;
30 import jalview.util.StringUtils;
31 import jalview.workers.InformationThread;
32
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.BitSet;
36 import java.util.Collections;
37 import java.util.Enumeration;
38 import java.util.Iterator;
39 import java.util.List;
40 import java.util.ListIterator;
41 import java.util.Vector;
42
43 import fr.orsay.lri.varna.models.rna.RNA;
44
45 /**
46  * 
47  * Implements the SequenceI interface for a char[] based sequence object
48  */
49 public class Sequence extends ASequence implements SequenceI
50 {
51   SequenceI datasetSequence;
52
53   String name;
54
55   private char[] sequence;
56
57   String description;
58
59   int start;
60
61   int end;
62
63   HiddenMarkovModel hmm;
64
65   boolean isHMMConsensusSequence = false;
66
67   Vector<PDBEntry> pdbIds;
68
69   String vamsasId;
70
71   DBRefEntry[] dbrefs;
72
73   RNA rna;
74
75   /**
76    * This annotation is displayed below the alignment but the positions are tied
77    * to the residues of this sequence
78    *
79    * TODO: change to List<>
80    */
81   Vector<AlignmentAnnotation> annotation;
82
83   private SequenceFeaturesI sequenceFeatureStore;
84
85   /*
86    * A cursor holding the approximate current view position to the sequence,
87    * as determined by findIndex or findPosition or findPositions.
88    * Using a cursor as a hint allows these methods to be more performant for
89    * large sequences.
90    */
91   private SequenceCursor cursor;
92
93   /*
94    * A number that should be incremented whenever the sequence is edited.
95    * If the value matches the cursor token, then we can trust the cursor,
96    * if not then it should be recomputed. 
97    */
98   private int changeCount;
99
100   /**
101    * Creates a new Sequence object.
102    * 
103    * @param name
104    *          display name string
105    * @param sequence
106    *          string to form a possibly gapped sequence out of
107    * @param start
108    *          first position of non-gap residue in the sequence
109    * @param end
110    *          last position of ungapped residues (nearly always only used for
111    *          display purposes)
112    */
113   public Sequence(String name, String sequence, int start, int end)
114   {
115     this();
116     initSeqAndName(name, sequence.toCharArray(), start, end);
117   }
118
119   public Sequence(String name, char[] sequence, int start, int end)
120   {
121     this();
122     initSeqAndName(name, sequence, start, end);
123   }
124
125   /**
126    * Stage 1 constructor - assign name, sequence, and set start and end fields.
127    * start and end are updated values from name2 if it ends with /start-end
128    * 
129    * @param name2
130    * @param sequence2
131    * @param start2
132    * @param end2
133    */
134   protected void initSeqAndName(String name2, char[] sequence2, int start2,
135           int end2)
136   {
137     this.name = name2;
138     this.sequence = sequence2;
139     this.start = start2;
140     this.end = end2;
141     parseId();
142     checkValidRange();
143   }
144
145   /**
146    * If 'name' ends in /i-j, where i >= j > 0 are integers, extracts i and j as
147    * start and end respectively and removes the suffix from the name
148    */
149   void parseId()
150   {
151     if (name == null)
152     {
153       System.err.println(
154               "POSSIBLE IMPLEMENTATION ERROR: null sequence name passed to constructor.");
155       name = "";
156     }
157     int slashPos = name.lastIndexOf('/');
158     if (slashPos > -1 && slashPos < name.length() - 1)
159     {
160       String suffix = name.substring(slashPos + 1);
161       String[] range = suffix.split("-");
162       if (range.length == 2)
163       {
164         try
165         {
166           int from = Integer.valueOf(range[0]);
167           int to = Integer.valueOf(range[1]);
168           if (from > 0 && to >= from)
169           {
170             name = name.substring(0, slashPos);
171             setStart(from);
172             setEnd(to);
173             checkValidRange();
174           }
175         } catch (NumberFormatException e)
176         {
177           // leave name unchanged if suffix is invalid
178         }
179       }
180     }
181   }
182
183   /**
184    * Ensures that 'end' is not before the end of the sequence, that is,
185    * (end-start+1) is at least as long as the count of ungapped positions. Note
186    * that end is permitted to be beyond the end of the sequence data.
187    */
188   void checkValidRange()
189   {
190     // Note: JAL-774 :
191     // http://issues.jalview.org/browse/JAL-774?focusedCommentId=11239&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-11239
192     {
193       int endRes = 0;
194       for (int j = 0; j < sequence.length; j++)
195       {
196         if (!Comparison.isGap(sequence[j]))
197         {
198           endRes++;
199         }
200       }
201       if (endRes > 0)
202       {
203         endRes += start - 1;
204       }
205
206       if (end < endRes)
207       {
208         end = endRes;
209       }
210     }
211
212   }
213
214   /**
215    * default constructor
216    */
217   private Sequence()
218   {
219     sequenceFeatureStore = new SequenceFeatures();
220   }
221
222   /**
223    * Creates a new Sequence object.
224    * 
225    * @param name
226    *          DOCUMENT ME!
227    * @param sequence
228    *          DOCUMENT ME!
229    */
230   public Sequence(String name, String sequence)
231   {
232     this(name, sequence, 1, -1);
233   }
234
235   /**
236    * Creates a new Sequence object with new AlignmentAnnotations but inherits
237    * any existing dataset sequence reference. If non exists, everything is
238    * copied.
239    * 
240    * @param seq
241    *          if seq is a dataset sequence, behaves like a plain old copy
242    *          constructor
243    */
244   public Sequence(SequenceI seq)
245   {
246     this(seq, seq.getAnnotation());
247   }
248
249   /**
250    * Create a new sequence object with new features, DBRefEntries, and PDBIds
251    * but inherits any existing dataset sequence reference, and duplicate of any
252    * annotation that is present in the given annotation array.
253    * 
254    * @param seq
255    *          the sequence to be copied
256    * @param alAnnotation
257    *          an array of annotation including some associated with seq
258    */
259   public Sequence(SequenceI seq, AlignmentAnnotation[] alAnnotation)
260   {
261     this();
262     initSeqFrom(seq, alAnnotation);
263   }
264
265   /**
266    * does the heavy lifting when cloning a dataset sequence, or coping data from
267    * dataset to a new derived sequence.
268    * 
269    * @param seq
270    *          - source of attributes.
271    * @param alAnnotation
272    *          - alignment annotation present on seq that should be copied onto
273    *          this sequence
274    */
275   protected void initSeqFrom(SequenceI seq,
276           AlignmentAnnotation[] alAnnotation)
277   {
278     char[] oseq = seq.getSequence(); // returns a copy of the array
279     initSeqAndName(seq.getName(), oseq, seq.getStart(), seq.getEnd());
280
281     description = seq.getDescription();
282     if (seq != datasetSequence)
283     {
284       setDatasetSequence(seq.getDatasetSequence());
285     }
286     
287     /*
288      * only copy DBRefs and seqfeatures if we really are a dataset sequence
289      */
290     if (datasetSequence == null)
291     {
292       if (seq.getDBRefs() != null)
293       {
294         DBRefEntry[] dbr = seq.getDBRefs();
295         for (int i = 0; i < dbr.length; i++)
296         {
297           addDBRef(new DBRefEntry(dbr[i]));
298         }
299       }
300
301       /*
302        * make copies of any sequence features
303        */
304       for (SequenceFeature sf : seq.getSequenceFeatures())
305       {
306         addSequenceFeature(new SequenceFeature(sf));
307       }
308     }
309
310     if (seq.getAnnotation() != null)
311     {
312       AlignmentAnnotation[] sqann = seq.getAnnotation();
313       for (int i = 0; i < sqann.length; i++)
314       {
315         if (sqann[i] == null)
316         {
317           continue;
318         }
319         boolean found = (alAnnotation == null);
320         if (!found)
321         {
322           for (int apos = 0; !found && apos < alAnnotation.length; apos++)
323           {
324             found = (alAnnotation[apos] == sqann[i]);
325           }
326         }
327         if (found)
328         {
329           // only copy the given annotation
330           AlignmentAnnotation newann = new AlignmentAnnotation(sqann[i]);
331           addAlignmentAnnotation(newann);
332         }
333       }
334     }
335     if (seq.getAllPDBEntries() != null)
336     {
337       Vector<PDBEntry> ids = seq.getAllPDBEntries();
338       for (PDBEntry pdb : ids)
339       {
340         this.addPDBId(new PDBEntry(pdb));
341       }
342     }
343     if (seq.getHMM() != null)
344     {
345       this.hmm = new HiddenMarkovModel(seq.getHMM(), this);
346     }
347
348   }
349
350   @Override
351   public void setSequenceFeatures(List<SequenceFeature> features)
352   {
353     if (datasetSequence != null)
354     {
355       datasetSequence.setSequenceFeatures(features);
356       return;
357     }
358     sequenceFeatureStore = new SequenceFeatures(features);
359   }
360
361   @Override
362   public synchronized boolean addSequenceFeature(SequenceFeature sf)
363   {
364     if (sf.getType() == null)
365     {
366       System.err.println("SequenceFeature type may not be null: "
367               + sf.toString());
368       return false;
369     }
370
371     if (datasetSequence != null)
372     {
373       return datasetSequence.addSequenceFeature(sf);
374     }
375
376     return sequenceFeatureStore.add(sf);
377   }
378
379   @Override
380   public void deleteFeature(SequenceFeature sf)
381   {
382     if (datasetSequence != null)
383     {
384       datasetSequence.deleteFeature(sf);
385     }
386     else
387     {
388       sequenceFeatureStore.delete(sf);
389     }
390   }
391
392   /**
393    * {@inheritDoc}
394    * 
395    * @return
396    */
397   @Override
398   public List<SequenceFeature> getSequenceFeatures()
399   {
400     if (datasetSequence != null)
401     {
402       return datasetSequence.getSequenceFeatures();
403     }
404     return sequenceFeatureStore.getAllFeatures();
405   }
406
407   @Override
408   public SequenceFeaturesI getFeatures()
409   {
410     return datasetSequence != null ? datasetSequence.getFeatures()
411             : sequenceFeatureStore;
412   }
413
414   @Override
415   public boolean addPDBId(PDBEntry entry)
416   {
417     if (pdbIds == null)
418     {
419       pdbIds = new Vector<>();
420       pdbIds.add(entry);
421       return true;
422     }
423
424     for (PDBEntry pdbe : pdbIds)
425     {
426       if (pdbe.updateFrom(entry))
427       {
428         return false;
429       }
430     }
431     pdbIds.addElement(entry);
432     return true;
433   }
434
435   /**
436    * DOCUMENT ME!
437    * 
438    * @param id
439    *          DOCUMENT ME!
440    */
441   @Override
442   public void setPDBId(Vector<PDBEntry> id)
443   {
444     pdbIds = id;
445   }
446
447   /**
448    * DOCUMENT ME!
449    * 
450    * @return DOCUMENT ME!
451    */
452   @Override
453   public Vector<PDBEntry> getAllPDBEntries()
454   {
455     return pdbIds == null ? new Vector<>() : pdbIds;
456   }
457
458   /**
459    * DOCUMENT ME!
460    * 
461    * @return DOCUMENT ME!
462    */
463   @Override
464   public String getDisplayId(boolean jvsuffix)
465   {
466     StringBuffer result = new StringBuffer(name);
467     if (jvsuffix)
468     {
469       result.append("/" + start + "-" + end);
470     }
471
472     return result.toString();
473   }
474
475   /**
476    * Sets the sequence name. If the name ends in /start-end, then the start-end
477    * values are parsed out and set, and the suffix is removed from the name.
478    * 
479    * @param theName
480    */
481   @Override
482   public void setName(String theName)
483   {
484     this.name = theName;
485     this.parseId();
486   }
487
488   /**
489    * DOCUMENT ME!
490    * 
491    * @return DOCUMENT ME!
492    */
493   @Override
494   public String getName()
495   {
496     return this.name;
497   }
498
499   /**
500    * DOCUMENT ME!
501    * 
502    * @param start
503    *          DOCUMENT ME!
504    */
505   @Override
506   public void setStart(int start)
507   {
508     this.start = start;
509   }
510
511   /**
512    * DOCUMENT ME!
513    * 
514    * @return DOCUMENT ME!
515    */
516   @Override
517   public int getStart()
518   {
519     return this.start;
520   }
521
522   /**
523    * DOCUMENT ME!
524    * 
525    * @param end
526    *          DOCUMENT ME!
527    */
528   @Override
529   public void setEnd(int end)
530   {
531     this.end = end;
532   }
533
534   /**
535    * DOCUMENT ME!
536    * 
537    * @return DOCUMENT ME!
538    */
539   @Override
540   public int getEnd()
541   {
542     return this.end;
543   }
544
545   /**
546    * DOCUMENT ME!
547    * 
548    * @return DOCUMENT ME!
549    */
550   @Override
551   public int getLength()
552   {
553     return this.sequence.length;
554   }
555
556   /**
557    * DOCUMENT ME!
558    * 
559    * @param seq
560    *          DOCUMENT ME!
561    */
562   @Override
563   public void setSequence(String seq)
564   {
565     this.sequence = seq.toCharArray();
566     checkValidRange();
567     sequenceChanged();
568   }
569
570   @Override
571   public String getSequenceAsString()
572   {
573     return new String(sequence);
574   }
575
576   @Override
577   public String getSequenceAsString(int start, int end)
578   {
579     return new String(getSequence(start, end));
580   }
581
582   @Override
583   public char[] getSequence()
584   {
585     // return sequence;
586     return sequence == null ? null : Arrays.copyOf(sequence,
587             sequence.length);
588   }
589
590   /*
591    * (non-Javadoc)
592    * 
593    * @see jalview.datamodel.SequenceI#getSequence(int, int)
594    */
595   @Override
596   public char[] getSequence(int start, int end)
597   {
598     if (start < 0)
599     {
600       start = 0;
601     }
602     // JBPNote - left to user to pad the result here (TODO:Decide on this
603     // policy)
604     if (start >= sequence.length)
605     {
606       return new char[0];
607     }
608
609     if (end >= sequence.length)
610     {
611       end = sequence.length;
612     }
613
614     char[] reply = new char[end - start];
615     System.arraycopy(sequence, start, reply, 0, end - start);
616
617     return reply;
618   }
619
620   @Override
621   public SequenceI getSubSequence(int start, int end)
622   {
623     if (start < 0)
624     {
625       start = 0;
626     }
627     char[] seq = getSequence(start, end);
628     if (seq.length == 0)
629     {
630       return null;
631     }
632     int nstart = findPosition(start);
633     int nend = findPosition(end) - 1;
634     // JBPNote - this is an incomplete copy.
635     SequenceI nseq = new Sequence(this.getName(), seq, nstart, nend);
636     nseq.setDescription(description);
637     if (datasetSequence != null)
638     {
639       nseq.setDatasetSequence(datasetSequence);
640     }
641     else
642     {
643       nseq.setDatasetSequence(this);
644     }
645     return nseq;
646   }
647
648   /**
649    * Returns the character of the aligned sequence at the given position (base
650    * zero), or space if the position is not within the sequence's bounds
651    * 
652    * @return
653    */
654   @Override
655   public char getCharAt(int i)
656   {
657     if (i >= 0 && i < sequence.length)
658     {
659       return sequence[i];
660     }
661     else
662     {
663       return ' ';
664     }
665   }
666
667   /**
668    * Sets the sequence description, and also parses out any special formats of
669    * interest
670    * 
671    * @param desc
672    */
673   @Override
674   public void setDescription(String desc)
675   {
676     this.description = desc;
677   }
678
679   @Override
680   public void setGeneLoci(String speciesId, String assemblyId,
681           String chromosomeId, MapList map)
682   {
683     addDBRef(new GeneLocus(speciesId, assemblyId, chromosomeId,
684             new Mapping(map)));
685   }
686
687   /**
688    * Returns the gene loci mapping for the sequence (may be null)
689    * 
690    * @return
691    */
692   @Override
693   public GeneLociI getGeneLoci()
694   {
695     DBRefEntry[] refs = getDBRefs();
696     if (refs != null)
697     {
698       for (final DBRefEntry ref : refs)
699       {
700         if (ref instanceof GeneLociI)
701         {
702           return (GeneLociI) ref;
703         }
704       }
705     }
706     return null;
707   }
708
709   /**
710    * Answers the description
711    * 
712    * @return
713    */
714   @Override
715   public String getDescription()
716   {
717     return this.description;
718   }
719
720   /**
721    * {@inheritDoc}
722    */
723   @Override
724   public int findIndex(int pos)
725   {
726     /*
727      * use a valid, hopefully nearby, cursor if available
728      */
729     if (isValidCursor(cursor))
730     {
731       return findIndex(pos, cursor);
732     }
733
734     int j = start;
735     int i = 0;
736     int startColumn = 0;
737
738     /*
739      * traverse sequence from the start counting gaps; make a note of
740      * the column of the first residue to save in the cursor
741      */
742     while ((i < sequence.length) && (j <= end) && (j <= pos))
743     {
744       if (!Comparison.isGap(sequence[i]))
745       {
746         if (j == start)
747         {
748           startColumn = i;
749         }
750         j++;
751       }
752       i++;
753     }
754
755     if (j == end && j < pos)
756     {
757       return end + 1;
758     }
759
760     updateCursor(pos, i, startColumn);
761     return i;
762   }
763
764   /**
765    * Updates the cursor to the latest found residue and column position
766    * 
767    * @param residuePos
768    *          (start..)
769    * @param column
770    *          (1..)
771    * @param startColumn
772    *          column position of the first sequence residue
773    */
774   protected void updateCursor(int residuePos, int column, int startColumn)
775   {
776     /*
777      * preserve end residue column provided cursor was valid
778      */
779     int endColumn = isValidCursor(cursor) ? cursor.lastColumnPosition : 0;
780
781     if (residuePos == this.end)
782     {
783       endColumn = column;
784     }
785
786     cursor = new SequenceCursor(this, residuePos, column, startColumn,
787             endColumn, this.changeCount);
788   }
789
790   /**
791    * Answers the aligned column position (1..) for the given residue position
792    * (start..) given a 'hint' of a residue/column location in the neighbourhood.
793    * The hint may be left of, at, or to the right of the required position.
794    * 
795    * @param pos
796    * @param curs
797    * @return
798    */
799   protected int findIndex(final int pos, SequenceCursor curs)
800   {
801     if (!isValidCursor(curs))
802     {
803       /*
804        * wrong or invalidated cursor, compute de novo
805        */
806       return findIndex(pos);
807     }
808
809     if (curs.residuePosition == pos)
810     {
811       return curs.columnPosition;
812     }
813
814     /*
815      * move left or right to find pos from hint.position
816      */
817     int col = curs.columnPosition - 1; // convert from base 1 to base 0
818     int newPos = curs.residuePosition;
819     int delta = newPos > pos ? -1 : 1;
820
821     while (newPos != pos)
822     {
823       col += delta; // shift one column left or right
824       if (col < 0)
825       {
826         break;
827       }
828       if (col == sequence.length)
829       {
830         col--; // return last column if we failed to reach pos
831         break;
832       }
833       if (!Comparison.isGap(sequence[col]))
834       {
835         newPos += delta;
836       }
837     }
838
839     col++; // convert back to base 1
840
841     /*
842      * only update cursor if we found the target position
843      */
844     if (newPos == pos)
845     {
846       updateCursor(pos, col, curs.firstColumnPosition);
847     }
848
849     return col;
850   }
851
852   /**
853    * {@inheritDoc}
854    */
855   @Override
856   public int findPosition(final int column)
857   {
858     /*
859      * use a valid, hopefully nearby, cursor if available
860      */
861     if (isValidCursor(cursor))
862     {
863       return findPosition(column + 1, cursor);
864     }
865     
866     // TODO recode this more naturally i.e. count residues only
867     // as they are found, not 'in anticipation'
868
869     /*
870      * traverse the sequence counting gaps; note the column position
871      * of the first residue, to save in the cursor
872      */
873     int firstResidueColumn = 0;
874     int lastPosFound = 0;
875     int lastPosFoundColumn = 0;
876     int seqlen = sequence.length;
877
878     if (seqlen > 0 && !Comparison.isGap(sequence[0]))
879     {
880       lastPosFound = start;
881       lastPosFoundColumn = 0;
882     }
883
884     int j = 0;
885     int pos = start;
886
887     while (j < column && j < seqlen)
888     {
889       if (!Comparison.isGap(sequence[j]))
890       {
891         lastPosFound = pos;
892         lastPosFoundColumn = j;
893         if (pos == this.start)
894         {
895           firstResidueColumn = j;
896         }
897         pos++;
898       }
899       j++;
900     }
901     if (j < seqlen && !Comparison.isGap(sequence[j]))
902     {
903       lastPosFound = pos;
904       lastPosFoundColumn = j;
905       if (pos == this.start)
906       {
907         firstResidueColumn = j;
908       }
909     }
910
911     /*
912      * update the cursor to the last residue position found (if any)
913      * (converting column position to base 1)
914      */
915     if (lastPosFound != 0)
916     {
917       updateCursor(lastPosFound, lastPosFoundColumn + 1,
918               firstResidueColumn + 1);
919     }
920
921     return pos;
922   }
923
924   /**
925    * Answers true if the given cursor is not null, is for this sequence object,
926    * and has a token value that matches this object's changeCount, else false.
927    * This allows us to ignore a cursor as 'stale' if the sequence has been
928    * modified since the cursor was created.
929    * 
930    * @param curs
931    * @return
932    */
933   protected boolean isValidCursor(SequenceCursor curs)
934   {
935     if (curs == null || curs.sequence != this || curs.token != changeCount)
936     {
937       return false;
938     }
939     /*
940      * sanity check against range
941      */
942     if (curs.columnPosition < 0 || curs.columnPosition > sequence.length)
943     {
944       return false;
945     }
946     if (curs.residuePosition < start || curs.residuePosition > end)
947     {
948       return false;
949     }
950     return true;
951   }
952
953   /**
954    * Answers the sequence position (start..) for the given aligned column
955    * position (1..), given a hint of a cursor in the neighbourhood. The cursor
956    * may lie left of, at, or to the right of the column position.
957    * 
958    * @param col
959    * @param curs
960    * @return
961    */
962   protected int findPosition(final int col, SequenceCursor curs)
963   {
964     if (!isValidCursor(curs))
965     {
966       /*
967        * wrong or invalidated cursor, compute de novo
968        */
969       return findPosition(col - 1);// ugh back to base 0
970     }
971
972     if (curs.columnPosition == col)
973     {
974       cursor = curs; // in case this method becomes public
975       return curs.residuePosition; // easy case :-)
976     }
977
978     if (curs.lastColumnPosition > 0 && curs.lastColumnPosition < col)
979     {
980       /*
981        * sequence lies entirely to the left of col
982        * - return last residue + 1
983        */
984       return end + 1;
985     }
986
987     if (curs.firstColumnPosition > 0 && curs.firstColumnPosition > col)
988     {
989       /*
990        * sequence lies entirely to the right of col
991        * - return first residue
992        */
993       return start;
994     }
995
996     // todo could choose closest to col out of column,
997     // firstColumnPosition, lastColumnPosition as a start point
998
999     /*
1000      * move left or right to find pos from cursor position
1001      */
1002     int firstResidueColumn = curs.firstColumnPosition;
1003     int column = curs.columnPosition - 1; // to base 0
1004     int newPos = curs.residuePosition;
1005     int delta = curs.columnPosition > col ? -1 : 1;
1006     boolean gapped = false;
1007     int lastFoundPosition = curs.residuePosition;
1008     int lastFoundPositionColumn = curs.columnPosition;
1009
1010     while (column != col - 1)
1011     {
1012       column += delta; // shift one column left or right
1013       if (column < 0 || column == sequence.length)
1014       {
1015         break;
1016       }
1017       gapped = Comparison.isGap(sequence[column]);
1018       if (!gapped)
1019       {
1020         newPos += delta;
1021         lastFoundPosition = newPos;
1022         lastFoundPositionColumn = column + 1;
1023         if (lastFoundPosition == this.start)
1024         {
1025           firstResidueColumn = column + 1;
1026         }
1027       }
1028     }
1029
1030     if (cursor == null || lastFoundPosition != cursor.residuePosition)
1031     {
1032       updateCursor(lastFoundPosition, lastFoundPositionColumn,
1033               firstResidueColumn);
1034     }
1035
1036     /*
1037      * hack to give position to the right if on a gap
1038      * or beyond the length of the sequence (see JAL-2562)
1039      */
1040     if (delta > 0 && (gapped || column >= sequence.length))
1041     {
1042       newPos++;
1043     }
1044
1045     return newPos;
1046   }
1047
1048   /**
1049    * {@inheritDoc}
1050    */
1051   @Override
1052   public ContiguousI findPositions(int fromColumn, int toColumn)
1053   {
1054     if (toColumn < fromColumn || fromColumn < 1)
1055     {
1056       return null;
1057     }
1058
1059     /*
1060      * find the first non-gapped position, if any
1061      */
1062     int firstPosition = 0;
1063     int col = fromColumn - 1;
1064     int length = sequence.length;
1065     while (col < length && col < toColumn)
1066     {
1067       if (!Comparison.isGap(sequence[col]))
1068       {
1069         firstPosition = findPosition(col++);
1070         break;
1071       }
1072       col++;
1073     }
1074
1075     if (firstPosition == 0)
1076     {
1077       return null;
1078     }
1079
1080     /*
1081      * find the last non-gapped position
1082      */
1083     int lastPosition = firstPosition;
1084     while (col < length && col < toColumn)
1085     {
1086       if (!Comparison.isGap(sequence[col++]))
1087       {
1088         lastPosition++;
1089       }
1090     }
1091
1092     return new Range(firstPosition, lastPosition);
1093   }
1094
1095   /**
1096    * Returns an int array where indices correspond to each residue in the
1097    * sequence and the element value gives its position in the alignment
1098    * 
1099    * @return int[SequenceI.getEnd()-SequenceI.getStart()+1] or null if no
1100    *         residues in SequenceI object
1101    */
1102   @Override
1103   public int[] gapMap()
1104   {
1105     String seq = jalview.analysis.AlignSeq.extractGaps(
1106             jalview.util.Comparison.GapChars, new String(sequence));
1107     int[] map = new int[seq.length()];
1108     int j = 0;
1109     int p = 0;
1110
1111     while (j < sequence.length)
1112     {
1113       if (!jalview.util.Comparison.isGap(sequence[j]))
1114       {
1115         map[p++] = j;
1116       }
1117
1118       j++;
1119     }
1120
1121     return map;
1122   }
1123
1124   /**
1125    * Build a bitset corresponding to sequence gaps
1126    * 
1127    * @return a BitSet where set values correspond to gaps in the sequence
1128    */
1129   @Override
1130   public BitSet gapBitset()
1131   {
1132     BitSet gaps = new BitSet(sequence.length);
1133     int j = 0;
1134     while (j < sequence.length)
1135     {
1136       if (jalview.util.Comparison.isGap(sequence[j]))
1137       {
1138         gaps.set(j);
1139       }
1140       j++;
1141     }
1142     return gaps;
1143   }
1144
1145   @Override
1146   public int[] findPositionMap()
1147   {
1148     int map[] = new int[sequence.length];
1149     int j = 0;
1150     int pos = start;
1151     int seqlen = sequence.length;
1152     while ((j < seqlen))
1153     {
1154       map[j] = pos;
1155       if (!jalview.util.Comparison.isGap(sequence[j]))
1156       {
1157         pos++;
1158       }
1159
1160       j++;
1161     }
1162     return map;
1163   }
1164
1165   @Override
1166   public List<int[]> getInsertions()
1167   {
1168     ArrayList<int[]> map = new ArrayList<>();
1169     int lastj = -1, j = 0;
1170     int pos = start;
1171     int seqlen = sequence.length;
1172     while ((j < seqlen))
1173     {
1174       if (jalview.util.Comparison.isGap(sequence[j]))
1175       {
1176         if (lastj == -1)
1177         {
1178           lastj = j;
1179         }
1180       }
1181       else
1182       {
1183         if (lastj != -1)
1184         {
1185           map.add(new int[] { lastj, j - 1 });
1186           lastj = -1;
1187         }
1188       }
1189       j++;
1190     }
1191     if (lastj != -1)
1192     {
1193       map.add(new int[] { lastj, j - 1 });
1194       lastj = -1;
1195     }
1196     return map;
1197   }
1198
1199   @Override
1200   public BitSet getInsertionsAsBits()
1201   {
1202     BitSet map = new BitSet();
1203     int lastj = -1, j = 0;
1204     int pos = start;
1205     int seqlen = sequence.length;
1206     while ((j < seqlen))
1207     {
1208       if (jalview.util.Comparison.isGap(sequence[j]))
1209       {
1210         if (lastj == -1)
1211         {
1212           lastj = j;
1213         }
1214       }
1215       else
1216       {
1217         if (lastj != -1)
1218         {
1219           map.set(lastj, j);
1220           lastj = -1;
1221         }
1222       }
1223       j++;
1224     }
1225     if (lastj != -1)
1226     {
1227       map.set(lastj, j);
1228       lastj = -1;
1229     }
1230     return map;
1231   }
1232
1233   @Override
1234   public void deleteChars(final int i, final int j)
1235   {
1236     int newstart = start, newend = end;
1237     if (i >= sequence.length || i < 0)
1238     {
1239       return;
1240     }
1241
1242     char[] tmp = StringUtils.deleteChars(sequence, i, j);
1243     boolean createNewDs = false;
1244     // TODO: take a (second look) at the dataset creation validation method for
1245     // the very large sequence case
1246
1247     int startIndex = findIndex(start) - 1;
1248     int endIndex = findIndex(end) - 1;
1249     int startDeleteColumn = -1; // for dataset sequence deletions
1250     int deleteCount = 0;
1251
1252     for (int s = i; s < j && s < sequence.length; s++)
1253     {
1254       if (Comparison.isGap(sequence[s]))
1255       {
1256         continue;
1257       }
1258       deleteCount++;
1259       if (startDeleteColumn == -1)
1260       {
1261         startDeleteColumn = findPosition(s) - start;
1262       }
1263       if (createNewDs)
1264       {
1265         newend--;
1266       }
1267       else
1268       {
1269         if (startIndex == s)
1270         {
1271           /*
1272            * deleting characters from start of sequence; new start is the
1273            * sequence position of the next column (position to the right
1274            * if the column position is gapped)
1275            */
1276           newstart = findPosition(j);
1277           break;
1278         }
1279         else
1280         {
1281           if (endIndex < j)
1282           {
1283             /*
1284              * deleting characters at end of sequence; new end is the sequence
1285              * position of the column before the deletion; subtract 1 if this is
1286              * gapped since findPosition returns the next sequence position
1287              */
1288             newend = findPosition(i - 1);
1289             if (Comparison.isGap(sequence[i - 1]))
1290             {
1291               newend--;
1292             }
1293             break;
1294           }
1295           else
1296           {
1297             createNewDs = true;
1298             newend--;
1299           }
1300         }
1301       }
1302     }
1303
1304     if (createNewDs && this.datasetSequence != null)
1305     {
1306       /*
1307        * if deletion occured in the middle of the sequence,
1308        * construct a new dataset sequence and delete the residues
1309        * that were deleted from the aligned sequence
1310        */
1311       Sequence ds = new Sequence(datasetSequence);
1312       ds.deleteChars(startDeleteColumn, startDeleteColumn + deleteCount);
1313       datasetSequence = ds;
1314       // TODO: remove any non-inheritable properties ?
1315       // TODO: create a sequence mapping (since there is a relation here ?)
1316     }
1317     start = newstart;
1318     end = newend;
1319     sequence = tmp;
1320     sequenceChanged();
1321   }
1322
1323   @Override
1324   public void insertCharAt(int i, int length, char c)
1325   {
1326     char[] tmp = new char[sequence.length + length];
1327
1328     if (i >= sequence.length)
1329     {
1330       System.arraycopy(sequence, 0, tmp, 0, sequence.length);
1331       i = sequence.length;
1332     }
1333     else
1334     {
1335       System.arraycopy(sequence, 0, tmp, 0, i);
1336     }
1337
1338     int index = i;
1339     while (length > 0)
1340     {
1341       tmp[index++] = c;
1342       length--;
1343     }
1344
1345     if (i < sequence.length)
1346     {
1347       System.arraycopy(sequence, i, tmp, index, sequence.length - i);
1348     }
1349
1350     sequence = tmp;
1351     sequenceChanged();
1352   }
1353
1354   @Override
1355   public void insertCharAt(int i, char c)
1356   {
1357     insertCharAt(i, 1, c);
1358   }
1359
1360   @Override
1361   public String getVamsasId()
1362   {
1363     return vamsasId;
1364   }
1365
1366   @Override
1367   public void setVamsasId(String id)
1368   {
1369     vamsasId = id;
1370   }
1371
1372   @Override
1373   public void setDBRefs(DBRefEntry[] dbref)
1374   {
1375     if (dbrefs == null && datasetSequence != null
1376             && this != datasetSequence)
1377     {
1378       datasetSequence.setDBRefs(dbref);
1379       return;
1380     }
1381     dbrefs = dbref;
1382     if (dbrefs != null)
1383     {
1384       DBRefUtils.ensurePrimaries(this);
1385     }
1386   }
1387
1388   @Override
1389   public DBRefEntry[] getDBRefs()
1390   {
1391     if (dbrefs == null && datasetSequence != null
1392             && this != datasetSequence)
1393     {
1394       return datasetSequence.getDBRefs();
1395     }
1396     return dbrefs;
1397   }
1398
1399   @Override
1400   public void addDBRef(DBRefEntry entry)
1401   {
1402     if (datasetSequence != null)
1403     {
1404       datasetSequence.addDBRef(entry);
1405       return;
1406     }
1407
1408     if (dbrefs == null)
1409     {
1410       dbrefs = new DBRefEntry[0];
1411     }
1412
1413     for (DBRefEntryI dbr : dbrefs)
1414     {
1415       if (dbr.updateFrom(entry))
1416       {
1417         /*
1418          * found a dbref that either matched, or could be
1419          * updated from, the new entry - no need to add it
1420          */
1421         return;
1422       }
1423     }
1424
1425     /*
1426      * extend the array to make room for one more
1427      */
1428     // TODO use an ArrayList instead
1429     int j = dbrefs.length;
1430     DBRefEntry[] temp = new DBRefEntry[j + 1];
1431     System.arraycopy(dbrefs, 0, temp, 0, j);
1432     temp[temp.length - 1] = entry;
1433
1434     dbrefs = temp;
1435
1436     DBRefUtils.ensurePrimaries(this);
1437   }
1438
1439   @Override
1440   public void setDatasetSequence(SequenceI seq)
1441   {
1442     if (seq == this)
1443     {
1444       throw new IllegalArgumentException(
1445               "Implementation Error: self reference passed to SequenceI.setDatasetSequence");
1446     }
1447     if (seq != null && seq.getDatasetSequence() != null)
1448     {
1449       throw new IllegalArgumentException(
1450               "Implementation error: cascading dataset sequences are not allowed.");
1451     }
1452     datasetSequence = seq;
1453   }
1454
1455   @Override
1456   public SequenceI getDatasetSequence()
1457   {
1458     return datasetSequence;
1459   }
1460
1461   @Override
1462   public AlignmentAnnotation[] getAnnotation()
1463   {
1464     return annotation == null ? null
1465             : annotation
1466                     .toArray(new AlignmentAnnotation[annotation.size()]);
1467   }
1468
1469   @Override
1470   public boolean hasAnnotation(AlignmentAnnotation ann)
1471   {
1472     return annotation == null ? false : annotation.contains(ann);
1473   }
1474
1475   @Override
1476   public void addAlignmentAnnotation(AlignmentAnnotation annotation)
1477   {
1478     if (this.annotation == null)
1479     {
1480       this.annotation = new Vector<>();
1481     }
1482     if (!this.annotation.contains(annotation))
1483     {
1484       this.annotation.addElement(annotation);
1485     }
1486     annotation.setSequenceRef(this);
1487   }
1488
1489   @Override
1490   public void removeAlignmentAnnotation(AlignmentAnnotation annotation)
1491   {
1492     if (this.annotation != null)
1493     {
1494       this.annotation.removeElement(annotation);
1495       if (this.annotation.size() == 0)
1496       {
1497         this.annotation = null;
1498       }
1499     }
1500   }
1501
1502   /**
1503    * test if this is a valid candidate for another sequence's dataset sequence.
1504    * 
1505    */
1506   private boolean isValidDatasetSequence()
1507   {
1508     if (datasetSequence != null)
1509     {
1510       return false;
1511     }
1512     for (int i = 0; i < sequence.length; i++)
1513     {
1514       if (jalview.util.Comparison.isGap(sequence[i]))
1515       {
1516         return false;
1517       }
1518     }
1519     return true;
1520   }
1521
1522   @Override
1523   public SequenceI deriveSequence()
1524   {
1525     Sequence seq = null;
1526     if (datasetSequence == null)
1527     {
1528       if (isValidDatasetSequence())
1529       {
1530         // Use this as dataset sequence
1531         seq = new Sequence(getName(), "", 1, -1);
1532         seq.setDatasetSequence(this);
1533         seq.initSeqFrom(this, getAnnotation());
1534         return seq;
1535       }
1536       else
1537       {
1538         // Create a new, valid dataset sequence
1539         createDatasetSequence();
1540       }
1541     }
1542     return new Sequence(this);
1543   }
1544
1545   private boolean _isNa;
1546
1547   private int _seqhash = 0;
1548
1549   /**
1550    * Answers false if the sequence is more than 85% nucleotide (ACGTU), else
1551    * true
1552    */
1553   @Override
1554   public boolean isProtein()
1555   {
1556     if (datasetSequence != null)
1557     {
1558       return datasetSequence.isProtein();
1559     }
1560     if (_seqhash != sequence.hashCode())
1561     {
1562       _seqhash = sequence.hashCode();
1563       _isNa = Comparison.isNucleotide(this);
1564     }
1565     return !_isNa;
1566   };
1567
1568   /*
1569    * (non-Javadoc)
1570    * 
1571    * @see jalview.datamodel.SequenceI#createDatasetSequence()
1572    */
1573   @Override
1574   public SequenceI createDatasetSequence()
1575   {
1576     if (datasetSequence == null)
1577     {
1578       Sequence dsseq = new Sequence(getName(),
1579               AlignSeq.extractGaps(jalview.util.Comparison.GapChars,
1580                       getSequenceAsString()),
1581               getStart(), getEnd());
1582
1583       datasetSequence = dsseq;
1584
1585       dsseq.setDescription(description);
1586       // move features and database references onto dataset sequence
1587       dsseq.sequenceFeatureStore = sequenceFeatureStore;
1588       sequenceFeatureStore = null;
1589       dsseq.dbrefs = dbrefs;
1590       dbrefs = null;
1591       // TODO: search and replace any references to this sequence with
1592       // references to the dataset sequence in Mappings on dbref
1593       dsseq.pdbIds = pdbIds;
1594       pdbIds = null;
1595       datasetSequence.updatePDBIds();
1596       if (annotation != null)
1597       {
1598         // annotation is cloned rather than moved, to preserve what's currently
1599         // on the alignment
1600         for (AlignmentAnnotation aa : annotation)
1601         {
1602           AlignmentAnnotation _aa = new AlignmentAnnotation(aa);
1603           _aa.sequenceRef = datasetSequence;
1604           _aa.adjustForAlignment(); // uses annotation's own record of
1605                                     // sequence-column mapping
1606           datasetSequence.addAlignmentAnnotation(_aa);
1607         }
1608       }
1609     }
1610     return datasetSequence;
1611   }
1612
1613   /*
1614    * (non-Javadoc)
1615    * 
1616    * @see
1617    * jalview.datamodel.SequenceI#setAlignmentAnnotation(AlignmmentAnnotation[]
1618    * annotations)
1619    */
1620   @Override
1621   public void setAlignmentAnnotation(AlignmentAnnotation[] annotations)
1622   {
1623     if (annotation != null)
1624     {
1625       annotation.removeAllElements();
1626     }
1627     if (annotations != null)
1628     {
1629       for (int i = 0; i < annotations.length; i++)
1630       {
1631         if (annotations[i] != null)
1632         {
1633           addAlignmentAnnotation(annotations[i]);
1634         }
1635       }
1636     }
1637   }
1638
1639   @Override
1640   public AlignmentAnnotation[] getAnnotation(String label)
1641   {
1642     if (annotation == null || annotation.size() == 0)
1643     {
1644       return null;
1645     }
1646
1647     Vector<AlignmentAnnotation> subset = new Vector<>();
1648     Enumeration<AlignmentAnnotation> e = annotation.elements();
1649     while (e.hasMoreElements())
1650     {
1651       AlignmentAnnotation ann = e.nextElement();
1652       if (ann.label != null && ann.label.equals(label))
1653       {
1654         subset.addElement(ann);
1655       }
1656     }
1657     if (subset.size() == 0)
1658     {
1659       return null;
1660     }
1661     AlignmentAnnotation[] anns = new AlignmentAnnotation[subset.size()];
1662     int i = 0;
1663     e = subset.elements();
1664     while (e.hasMoreElements())
1665     {
1666       anns[i++] = e.nextElement();
1667     }
1668     subset.removeAllElements();
1669     return anns;
1670   }
1671
1672   @Override
1673   public boolean updatePDBIds()
1674   {
1675     if (datasetSequence != null)
1676     {
1677       // TODO: could merge DBRefs
1678       return datasetSequence.updatePDBIds();
1679     }
1680     if (dbrefs == null || dbrefs.length == 0)
1681     {
1682       return false;
1683     }
1684     boolean added = false;
1685     for (DBRefEntry dbr : dbrefs)
1686     {
1687       if (DBRefSource.PDB.equals(dbr.getSource()))
1688       {
1689         /*
1690          * 'Add' any PDB dbrefs as a PDBEntry - add is only performed if the
1691          * PDB id is not already present in a 'matching' PDBEntry
1692          * Constructor parses out a chain code if appended to the accession id
1693          * (a fudge used to 'store' the chain code in the DBRef)
1694          */
1695         PDBEntry pdbe = new PDBEntry(dbr);
1696         added |= addPDBId(pdbe);
1697       }
1698     }
1699     return added;
1700   }
1701
1702   @Override
1703   public void transferAnnotation(SequenceI entry, Mapping mp)
1704   {
1705     if (datasetSequence != null)
1706     {
1707       datasetSequence.transferAnnotation(entry, mp);
1708       return;
1709     }
1710     if (entry.getDatasetSequence() != null)
1711     {
1712       transferAnnotation(entry.getDatasetSequence(), mp);
1713       return;
1714     }
1715     // transfer any new features from entry onto sequence
1716     if (entry.getSequenceFeatures() != null)
1717     {
1718
1719       List<SequenceFeature> sfs = entry.getSequenceFeatures();
1720       for (SequenceFeature feature : sfs)
1721       {
1722        SequenceFeature sf[] = (mp != null) ? mp.locateFeature(feature)
1723                 : new SequenceFeature[] { new SequenceFeature(feature) };
1724         if (sf != null)
1725         {
1726           for (int sfi = 0; sfi < sf.length; sfi++)
1727           {
1728             addSequenceFeature(sf[sfi]);
1729           }
1730         }
1731       }
1732     }
1733
1734     // transfer PDB entries
1735     if (entry.getAllPDBEntries() != null)
1736     {
1737       Enumeration<PDBEntry> e = entry.getAllPDBEntries().elements();
1738       while (e.hasMoreElements())
1739       {
1740         PDBEntry pdb = e.nextElement();
1741         addPDBId(pdb);
1742       }
1743     }
1744     // transfer database references
1745     DBRefEntry[] entryRefs = entry.getDBRefs();
1746     if (entryRefs != null)
1747     {
1748       for (int r = 0; r < entryRefs.length; r++)
1749       {
1750         DBRefEntry newref = new DBRefEntry(entryRefs[r]);
1751         if (newref.getMap() != null && mp != null)
1752         {
1753           // remap ref using our local mapping
1754         }
1755         // we also assume all version string setting is done by dbSourceProxy
1756         /*
1757          * if (!newref.getSource().equalsIgnoreCase(dbSource)) {
1758          * newref.setSource(dbSource); }
1759          */
1760         addDBRef(newref);
1761       }
1762     }
1763   }
1764
1765   @Override
1766   public void setRNA(RNA r)
1767   {
1768     rna = r;
1769   }
1770
1771   @Override
1772   public RNA getRNA()
1773   {
1774     return rna;
1775   }
1776
1777   @Override
1778   public List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
1779           String label)
1780   {
1781     List<AlignmentAnnotation> result = new ArrayList<>();
1782     if (this.annotation != null)
1783     {
1784       for (AlignmentAnnotation ann : annotation)
1785       {
1786         String id = ann.getCalcId();
1787         if (id != null && id.equals(calcId)
1788                 && ann.label != null && ann.label.equals(label))
1789         {
1790           result.add(ann);
1791         }
1792       }
1793     }
1794     return result;
1795   }
1796
1797   @Override
1798   public String toString()
1799   {
1800     return getDisplayId(false);
1801   }
1802
1803   @Override
1804   public PDBEntry getPDBEntry(String pdbIdStr)
1805   {
1806     if (getDatasetSequence() != null)
1807     {
1808       return getDatasetSequence().getPDBEntry(pdbIdStr);
1809     }
1810     if (pdbIds == null)
1811     {
1812       return null;
1813     }
1814     List<PDBEntry> entries = getAllPDBEntries();
1815     for (PDBEntry entry : entries)
1816     {
1817       if (entry.getId().equalsIgnoreCase(pdbIdStr))
1818       {
1819         return entry;
1820       }
1821     }
1822     return null;
1823   }
1824
1825   @Override
1826   public List<DBRefEntry> getPrimaryDBRefs()
1827   {
1828     if (datasetSequence != null)
1829     {
1830       return datasetSequence.getPrimaryDBRefs();
1831     }
1832     if (dbrefs == null || dbrefs.length == 0)
1833     {
1834       return Collections.emptyList();
1835     }
1836     synchronized (dbrefs)
1837     {
1838       List<DBRefEntry> primaries = new ArrayList<>();
1839       DBRefEntry[] tmp = new DBRefEntry[1];
1840       for (DBRefEntry ref : dbrefs)
1841       {
1842         if (!ref.isPrimaryCandidate())
1843         {
1844           continue;
1845         }
1846         if (ref.hasMap())
1847         {
1848           MapList mp = ref.getMap().getMap();
1849           if (mp.getFromLowest() > start || mp.getFromHighest() < end)
1850           {
1851             // map only involves a subsequence, so cannot be primary
1852             continue;
1853           }
1854         }
1855         // whilst it looks like it is a primary ref, we also sanity check type
1856         if (DBRefUtils.getCanonicalName(DBRefSource.PDB)
1857                 .equals(DBRefUtils.getCanonicalName(ref.getSource())))
1858         {
1859           // PDB dbrefs imply there should be a PDBEntry associated
1860           // TODO: tighten PDB dbrefs
1861           // formally imply Jalview has actually downloaded and
1862           // parsed the pdb file. That means there should be a cached file
1863           // handle on the PDBEntry, and a real mapping between sequence and
1864           // extracted sequence from PDB file
1865           PDBEntry pdbentry = getPDBEntry(ref.getAccessionId());
1866           if (pdbentry != null && pdbentry.getFile() != null)
1867           {
1868             primaries.add(ref);
1869           }
1870           continue;
1871         }
1872         // check standard protein or dna sources
1873         tmp[0] = ref;
1874         DBRefEntry[] res = DBRefUtils.selectDbRefs(!isProtein(), tmp);
1875         if (res != null && res[0] == tmp[0])
1876         {
1877           primaries.add(ref);
1878           continue;
1879         }
1880       }
1881       return primaries;
1882     }
1883   }
1884
1885   @Override
1886   public HiddenMarkovModel getHMM()
1887   {
1888     return hmm;
1889   }
1890
1891   @Override
1892   public void setHMM(HiddenMarkovModel hmm)
1893   {
1894     this.hmm = hmm;
1895   }
1896
1897   @Override
1898   public boolean hasHMMAnnotation()
1899   {
1900     if (this.annotation == null) {
1901       return false;
1902     }
1903     for (AlignmentAnnotation ann : annotation)
1904     {
1905       if (InformationThread.HMM_CALC_ID.equals(ann.getCalcId()))
1906       {
1907         return true;
1908       }
1909     }
1910     return false;
1911   }
1912
1913   /**
1914    * {@inheritDoc}
1915    */
1916   @Override
1917   public List<SequenceFeature> findFeatures(int fromColumn, int toColumn,
1918           String... types)
1919   {
1920     int startPos = findPosition(fromColumn - 1); // convert base 1 to base 0
1921     int endPos = fromColumn == toColumn ? startPos
1922             : findPosition(toColumn - 1);
1923
1924     List<SequenceFeature> result = getFeatures().findFeatures(startPos,
1925             endPos, types);
1926     if (datasetSequence != null)
1927     {
1928       result = datasetSequence.getFeatures().findFeatures(startPos, endPos,
1929               types);
1930     }
1931     else
1932     {
1933       result = sequenceFeatureStore.findFeatures(startPos, endPos, types);
1934     }
1935
1936     /*
1937      * if end column is gapped, endPos may be to the right, 
1938      * and we may have included adjacent or enclosing features;
1939      * remove any that are not enclosing, non-contact features
1940      */
1941     boolean endColumnIsGapped = toColumn > 0 && toColumn <= sequence.length
1942             && Comparison.isGap(sequence[toColumn - 1]);
1943     if (endPos > this.end || endColumnIsGapped)
1944     {
1945       ListIterator<SequenceFeature> it = result.listIterator();
1946       while (it.hasNext())
1947       {
1948         SequenceFeature sf = it.next();
1949         int sfBegin = sf.getBegin();
1950         int sfEnd = sf.getEnd();
1951         int featureStartColumn = findIndex(sfBegin);
1952         if (featureStartColumn > toColumn)
1953         {
1954           it.remove();
1955         }
1956         else if (featureStartColumn < fromColumn)
1957         {
1958           int featureEndColumn = sfEnd == sfBegin ? featureStartColumn
1959                   : findIndex(sfEnd);
1960           if (featureEndColumn < fromColumn)
1961           {
1962             it.remove();
1963           }
1964           else if (featureEndColumn > toColumn && sf.isContactFeature())
1965           {
1966             /*
1967              * remove an enclosing feature if it is a contact feature
1968              */
1969             it.remove();
1970           }
1971         }
1972       }
1973     }
1974
1975     return result;
1976   }
1977
1978   /**
1979    * Invalidates any stale cursors (forcing recalculation) by incrementing the
1980    * token that has to match the one presented by the cursor
1981    */
1982   @Override
1983   public void sequenceChanged()
1984   {
1985     changeCount++;
1986   }
1987
1988   /**
1989    * {@inheritDoc}
1990    */
1991   @Override
1992   public int replace(char c1, char c2)
1993   {
1994     if (c1 == c2)
1995     {
1996       return 0;
1997     }
1998     int count = 0;
1999     synchronized (sequence)
2000     {
2001       for (int c = 0; c < sequence.length; c++)
2002       {
2003         if (sequence[c] == c1)
2004         {
2005           sequence[c] = c2;
2006           count++;
2007         }
2008       }
2009     }
2010     if (count > 0)
2011     {
2012       sequenceChanged();
2013     }
2014
2015     return count;
2016   }
2017
2018   @Override
2019   public String getSequenceStringFromIterator(Iterator<int[]> it)
2020   {
2021     StringBuilder newSequence = new StringBuilder();
2022     while (it.hasNext())
2023     {
2024       int[] block = it.next();
2025       if (it.hasNext())
2026       {
2027         newSequence.append(getSequence(block[0], block[1] + 1));
2028       }
2029       else
2030       {
2031         newSequence.append(getSequence(block[0], block[1]));
2032       }
2033     }
2034
2035     return newSequence.toString();
2036   }
2037
2038   @Override
2039   public int firstResidueOutsideIterator(Iterator<int[]> regions)
2040   {
2041     int start = 0;
2042
2043     if (!regions.hasNext())
2044     {
2045       return findIndex(getStart()) - 1;
2046     }
2047
2048     // Simply walk along the sequence whilst watching for region
2049     // boundaries
2050     int hideStart = getLength();
2051     int hideEnd = -1;
2052     boolean foundStart = false;
2053
2054     // step through the non-gapped positions of the sequence
2055     for (int i = getStart(); i <= getEnd() && (!foundStart); i++)
2056     {
2057       // get alignment position of this residue in the sequence
2058       int p = findIndex(i) - 1;
2059
2060       // update region start/end
2061       while (hideEnd < p && regions.hasNext())
2062       {
2063         int[] region = regions.next();
2064         hideStart = region[0];
2065         hideEnd = region[1];
2066       }
2067       if (hideEnd < p)
2068       {
2069         hideStart = getLength();
2070       }
2071       // update boundary for sequence
2072       if (p < hideStart)
2073       {
2074         start = p;
2075         foundStart = true;
2076       }
2077     }
2078
2079     if (foundStart)
2080     {
2081       return start;
2082     }
2083     // otherwise, sequence was completely hidden
2084     return 0;
2085   }
2086
2087   @Override
2088   public boolean hasHMMProfile()
2089   {
2090     return hmm != null;
2091   }
2092 }