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