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