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