JAL-2994 put ‘\’ first in character class clause of regex - otherwise doesn’t compile...
[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(final 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)
855       {
856         break;
857       }
858       if (col == sequence.length)
859       {
860         col--; // return last column if we failed to reach pos
861         break;
862       }
863       if (!Comparison.isGap(sequence[col]))
864       {
865         newPos += delta;
866       }
867     }
868
869     col++; // convert back to base 1
870
871     /*
872      * only update cursor if we found the target position
873      */
874     if (newPos == pos)
875     {
876       updateCursor(pos, col, curs.firstColumnPosition);
877     }
878
879     return col;
880   }
881
882   /**
883    * {@inheritDoc}
884    */
885   @Override
886   public int findPosition(final int column)
887   {
888     /*
889      * use a valid, hopefully nearby, cursor if available
890      */
891     if (isValidCursor(cursor))
892     {
893       return findPosition(column + 1, cursor);
894     }
895     
896     // TODO recode this more naturally i.e. count residues only
897     // as they are found, not 'in anticipation'
898
899     /*
900      * traverse the sequence counting gaps; note the column position
901      * of the first residue, to save in the cursor
902      */
903     int firstResidueColumn = 0;
904     int lastPosFound = 0;
905     int lastPosFoundColumn = 0;
906     int seqlen = sequence.length;
907
908     if (seqlen > 0 && !Comparison.isGap(sequence[0]))
909     {
910       lastPosFound = start;
911       lastPosFoundColumn = 0;
912     }
913
914     int j = 0;
915     int pos = start;
916
917     while (j < column && j < seqlen)
918     {
919       if (!Comparison.isGap(sequence[j]))
920       {
921         lastPosFound = pos;
922         lastPosFoundColumn = j;
923         if (pos == this.start)
924         {
925           firstResidueColumn = j;
926         }
927         pos++;
928       }
929       j++;
930     }
931     if (j < seqlen && !Comparison.isGap(sequence[j]))
932     {
933       lastPosFound = pos;
934       lastPosFoundColumn = j;
935       if (pos == this.start)
936       {
937         firstResidueColumn = j;
938       }
939     }
940
941     /*
942      * update the cursor to the last residue position found (if any)
943      * (converting column position to base 1)
944      */
945     if (lastPosFound != 0)
946     {
947       updateCursor(lastPosFound, lastPosFoundColumn + 1,
948               firstResidueColumn + 1);
949     }
950
951     return pos;
952   }
953
954   /**
955    * Answers true if the given cursor is not null, is for this sequence object,
956    * and has a token value that matches this object's changeCount, else false.
957    * This allows us to ignore a cursor as 'stale' if the sequence has been
958    * modified since the cursor was created.
959    * 
960    * @param curs
961    * @return
962    */
963   protected boolean isValidCursor(SequenceCursor curs)
964   {
965     if (curs == null || curs.sequence != this || curs.token != changeCount)
966     {
967       return false;
968     }
969     /*
970      * sanity check against range
971      */
972     if (curs.columnPosition < 0 || curs.columnPosition > sequence.length)
973     {
974       return false;
975     }
976     if (curs.residuePosition < start || curs.residuePosition > end)
977     {
978       return false;
979     }
980     return true;
981   }
982
983   /**
984    * Answers the sequence position (start..) for the given aligned column
985    * position (1..), given a hint of a cursor in the neighbourhood. The cursor
986    * may lie left of, at, or to the right of the column position.
987    * 
988    * @param col
989    * @param curs
990    * @return
991    */
992   protected int findPosition(final int col, SequenceCursor curs)
993   {
994     if (!isValidCursor(curs))
995     {
996       /*
997        * wrong or invalidated cursor, compute de novo
998        */
999       return findPosition(col - 1);// ugh back to base 0
1000     }
1001
1002     if (curs.columnPosition == col)
1003     {
1004       cursor = curs; // in case this method becomes public
1005       return curs.residuePosition; // easy case :-)
1006     }
1007
1008     if (curs.lastColumnPosition > 0 && curs.lastColumnPosition < col)
1009     {
1010       /*
1011        * sequence lies entirely to the left of col
1012        * - return last residue + 1
1013        */
1014       return end + 1;
1015     }
1016
1017     if (curs.firstColumnPosition > 0 && curs.firstColumnPosition > col)
1018     {
1019       /*
1020        * sequence lies entirely to the right of col
1021        * - return first residue
1022        */
1023       return start;
1024     }
1025
1026     // todo could choose closest to col out of column,
1027     // firstColumnPosition, lastColumnPosition as a start point
1028
1029     /*
1030      * move left or right to find pos from cursor position
1031      */
1032     int firstResidueColumn = curs.firstColumnPosition;
1033     int column = curs.columnPosition - 1; // to base 0
1034     int newPos = curs.residuePosition;
1035     int delta = curs.columnPosition > col ? -1 : 1;
1036     boolean gapped = false;
1037     int lastFoundPosition = curs.residuePosition;
1038     int lastFoundPositionColumn = curs.columnPosition;
1039
1040     while (column != col - 1)
1041     {
1042       column += delta; // shift one column left or right
1043       if (column < 0 || column == sequence.length)
1044       {
1045         break;
1046       }
1047       gapped = Comparison.isGap(sequence[column]);
1048       if (!gapped)
1049       {
1050         newPos += delta;
1051         lastFoundPosition = newPos;
1052         lastFoundPositionColumn = column + 1;
1053         if (lastFoundPosition == this.start)
1054         {
1055           firstResidueColumn = column + 1;
1056         }
1057       }
1058     }
1059
1060     if (cursor == null || lastFoundPosition != cursor.residuePosition)
1061     {
1062       updateCursor(lastFoundPosition, lastFoundPositionColumn,
1063               firstResidueColumn);
1064     }
1065
1066     /*
1067      * hack to give position to the right if on a gap
1068      * or beyond the length of the sequence (see JAL-2562)
1069      */
1070     if (delta > 0 && (gapped || column >= sequence.length))
1071     {
1072       newPos++;
1073     }
1074
1075     return newPos;
1076   }
1077
1078   /**
1079    * {@inheritDoc}
1080    */
1081   @Override
1082   public Range findPositions(int fromColumn, int toColumn)
1083   {
1084     if (toColumn < fromColumn || fromColumn < 1)
1085     {
1086       return null;
1087     }
1088
1089     /*
1090      * find the first non-gapped position, if any
1091      */
1092     int firstPosition = 0;
1093     int col = fromColumn - 1;
1094     int length = sequence.length;
1095     while (col < length && col < toColumn)
1096     {
1097       if (!Comparison.isGap(sequence[col]))
1098       {
1099         firstPosition = findPosition(col++);
1100         break;
1101       }
1102       col++;
1103     }
1104
1105     if (firstPosition == 0)
1106     {
1107       return null;
1108     }
1109
1110     /*
1111      * find the last non-gapped position
1112      */
1113     int lastPosition = firstPosition;
1114     while (col < length && col < toColumn)
1115     {
1116       if (!Comparison.isGap(sequence[col++]))
1117       {
1118         lastPosition++;
1119       }
1120     }
1121
1122     return new Range(firstPosition, lastPosition);
1123   }
1124
1125   /**
1126    * Returns an int array where indices correspond to each residue in the
1127    * sequence and the element value gives its position in the alignment
1128    * 
1129    * @return int[SequenceI.getEnd()-SequenceI.getStart()+1] or null if no
1130    *         residues in SequenceI object
1131    */
1132   @Override
1133   public int[] gapMap()
1134   {
1135     String seq = jalview.analysis.AlignSeq.extractGaps(
1136             jalview.util.Comparison.GapChars, new String(sequence));
1137     int[] map = new int[seq.length()];
1138     int j = 0;
1139     int p = 0;
1140
1141     while (j < sequence.length)
1142     {
1143       if (!jalview.util.Comparison.isGap(sequence[j]))
1144       {
1145         map[p++] = j;
1146       }
1147
1148       j++;
1149     }
1150
1151     return map;
1152   }
1153
1154   /**
1155    * Build a bitset corresponding to sequence gaps
1156    * 
1157    * @return a BitSet where set values correspond to gaps in the sequence
1158    */
1159   @Override
1160   public BitSet gapBitset()
1161   {
1162     BitSet gaps = new BitSet(sequence.length);
1163     int j = 0;
1164     while (j < sequence.length)
1165     {
1166       if (jalview.util.Comparison.isGap(sequence[j]))
1167       {
1168         gaps.set(j);
1169       }
1170       j++;
1171     }
1172     return gaps;
1173   }
1174
1175   @Override
1176   public int[] findPositionMap()
1177   {
1178     int map[] = new int[sequence.length];
1179     int j = 0;
1180     int pos = start;
1181     int seqlen = sequence.length;
1182     while ((j < seqlen))
1183     {
1184       map[j] = pos;
1185       if (!jalview.util.Comparison.isGap(sequence[j]))
1186       {
1187         pos++;
1188       }
1189
1190       j++;
1191     }
1192     return map;
1193   }
1194
1195   @Override
1196   public List<int[]> getInsertions()
1197   {
1198     ArrayList<int[]> map = new ArrayList<>();
1199     int lastj = -1, j = 0;
1200     int pos = start;
1201     int seqlen = sequence.length;
1202     while ((j < seqlen))
1203     {
1204       if (jalview.util.Comparison.isGap(sequence[j]))
1205       {
1206         if (lastj == -1)
1207         {
1208           lastj = j;
1209         }
1210       }
1211       else
1212       {
1213         if (lastj != -1)
1214         {
1215           map.add(new int[] { lastj, j - 1 });
1216           lastj = -1;
1217         }
1218       }
1219       j++;
1220     }
1221     if (lastj != -1)
1222     {
1223       map.add(new int[] { lastj, j - 1 });
1224       lastj = -1;
1225     }
1226     return map;
1227   }
1228
1229   @Override
1230   public BitSet getInsertionsAsBits()
1231   {
1232     BitSet map = new BitSet();
1233     int lastj = -1, j = 0;
1234     int pos = start;
1235     int seqlen = sequence.length;
1236     while ((j < seqlen))
1237     {
1238       if (jalview.util.Comparison.isGap(sequence[j]))
1239       {
1240         if (lastj == -1)
1241         {
1242           lastj = j;
1243         }
1244       }
1245       else
1246       {
1247         if (lastj != -1)
1248         {
1249           map.set(lastj, j);
1250           lastj = -1;
1251         }
1252       }
1253       j++;
1254     }
1255     if (lastj != -1)
1256     {
1257       map.set(lastj, j);
1258       lastj = -1;
1259     }
1260     return map;
1261   }
1262
1263   @Override
1264   public void deleteChars(final int i, final int j)
1265   {
1266     int newstart = start, newend = end;
1267     if (i >= sequence.length || i < 0)
1268     {
1269       return;
1270     }
1271
1272     char[] tmp = StringUtils.deleteChars(sequence, i, j);
1273     boolean createNewDs = false;
1274     // TODO: take a (second look) at the dataset creation validation method for
1275     // the very large sequence case
1276     int startIndex = findIndex(start) - 1;
1277     int endIndex = findIndex(end) - 1;
1278     int startDeleteColumn = -1; // for dataset sequence deletions
1279     int deleteCount = 0;
1280
1281     for (int s = i; s < j; s++)
1282     {
1283       if (Comparison.isGap(sequence[s]))
1284       {
1285         continue;
1286       }
1287       deleteCount++;
1288       if (startDeleteColumn == -1)
1289       {
1290         startDeleteColumn = findPosition(s) - start;
1291       }
1292       if (createNewDs)
1293       {
1294         newend--;
1295       }
1296       else
1297       {
1298         if (startIndex == s)
1299         {
1300           /*
1301            * deleting characters from start of sequence; new start is the
1302            * sequence position of the next column (position to the right
1303            * if the column position is gapped)
1304            */
1305           newstart = findPosition(j);
1306           break;
1307         }
1308         else
1309         {
1310           if (endIndex < j)
1311           {
1312             /*
1313              * deleting characters at end of sequence; new end is the sequence
1314              * position of the column before the deletion; subtract 1 if this is
1315              * gapped since findPosition returns the next sequence position
1316              */
1317             newend = findPosition(i - 1);
1318             if (Comparison.isGap(sequence[i - 1]))
1319             {
1320               newend--;
1321             }
1322             break;
1323           }
1324           else
1325           {
1326             createNewDs = true;
1327             newend--;
1328           }
1329         }
1330       }
1331     }
1332
1333     if (createNewDs && this.datasetSequence != null)
1334     {
1335       /*
1336        * if deletion occured in the middle of the sequence,
1337        * construct a new dataset sequence and delete the residues
1338        * that were deleted from the aligned sequence
1339        */
1340       Sequence ds = new Sequence(datasetSequence);
1341       ds.deleteChars(startDeleteColumn, startDeleteColumn + deleteCount);
1342       datasetSequence = ds;
1343       // TODO: remove any non-inheritable properties ?
1344       // TODO: create a sequence mapping (since there is a relation here ?)
1345     }
1346     start = newstart;
1347     end = newend;
1348     sequence = tmp;
1349     sequenceChanged();
1350   }
1351
1352   @Override
1353   public void insertCharAt(int i, int length, char c)
1354   {
1355     char[] tmp = new char[sequence.length + length];
1356
1357     if (i >= sequence.length)
1358     {
1359       System.arraycopy(sequence, 0, tmp, 0, sequence.length);
1360       i = sequence.length;
1361     }
1362     else
1363     {
1364       System.arraycopy(sequence, 0, tmp, 0, i);
1365     }
1366
1367     int index = i;
1368     while (length > 0)
1369     {
1370       tmp[index++] = c;
1371       length--;
1372     }
1373
1374     if (i < sequence.length)
1375     {
1376       System.arraycopy(sequence, i, tmp, index, sequence.length - i);
1377     }
1378
1379     sequence = tmp;
1380     sequenceChanged();
1381   }
1382
1383   @Override
1384   public void insertCharAt(int i, char c)
1385   {
1386     insertCharAt(i, 1, c);
1387   }
1388
1389   @Override
1390   public String getVamsasId()
1391   {
1392     return vamsasId;
1393   }
1394
1395   @Override
1396   public void setVamsasId(String id)
1397   {
1398     vamsasId = id;
1399   }
1400
1401   @Override
1402   public void setDBRefs(DBRefEntry[] dbref)
1403   {
1404     if (dbrefs == null && datasetSequence != null
1405             && this != datasetSequence)
1406     {
1407       datasetSequence.setDBRefs(dbref);
1408       return;
1409     }
1410     dbrefs = dbref;
1411     if (dbrefs != null)
1412     {
1413       DBRefUtils.ensurePrimaries(this);
1414     }
1415   }
1416
1417   @Override
1418   public DBRefEntry[] getDBRefs()
1419   {
1420     if (dbrefs == null && datasetSequence != null
1421             && this != datasetSequence)
1422     {
1423       return datasetSequence.getDBRefs();
1424     }
1425     return dbrefs;
1426   }
1427
1428   @Override
1429   public void addDBRef(DBRefEntry entry)
1430   {
1431     if (datasetSequence != null)
1432     {
1433       datasetSequence.addDBRef(entry);
1434       return;
1435     }
1436
1437     if (dbrefs == null)
1438     {
1439       dbrefs = new DBRefEntry[0];
1440     }
1441
1442     for (DBRefEntryI dbr : dbrefs)
1443     {
1444       if (dbr.updateFrom(entry))
1445       {
1446         /*
1447          * found a dbref that either matched, or could be
1448          * updated from, the new entry - no need to add it
1449          */
1450         return;
1451       }
1452     }
1453
1454     /*
1455      * extend the array to make room for one more
1456      */
1457     // TODO use an ArrayList instead
1458     int j = dbrefs.length;
1459     DBRefEntry[] temp = new DBRefEntry[j + 1];
1460     System.arraycopy(dbrefs, 0, temp, 0, j);
1461     temp[temp.length - 1] = entry;
1462
1463     dbrefs = temp;
1464
1465     DBRefUtils.ensurePrimaries(this);
1466   }
1467
1468   @Override
1469   public void setDatasetSequence(SequenceI seq)
1470   {
1471     if (seq == this)
1472     {
1473       throw new IllegalArgumentException(
1474               "Implementation Error: self reference passed to SequenceI.setDatasetSequence");
1475     }
1476     if (seq != null && seq.getDatasetSequence() != null)
1477     {
1478       throw new IllegalArgumentException(
1479               "Implementation error: cascading dataset sequences are not allowed.");
1480     }
1481     datasetSequence = seq;
1482   }
1483
1484   @Override
1485   public SequenceI getDatasetSequence()
1486   {
1487     return datasetSequence;
1488   }
1489
1490   @Override
1491   public AlignmentAnnotation[] getAnnotation()
1492   {
1493     return annotation == null ? null
1494             : annotation
1495                     .toArray(new AlignmentAnnotation[annotation.size()]);
1496   }
1497
1498   @Override
1499   public boolean hasAnnotation(AlignmentAnnotation ann)
1500   {
1501     return annotation == null ? false : annotation.contains(ann);
1502   }
1503
1504   @Override
1505   public void addAlignmentAnnotation(AlignmentAnnotation annotation)
1506   {
1507     if (this.annotation == null)
1508     {
1509       this.annotation = new Vector<>();
1510     }
1511     if (!this.annotation.contains(annotation))
1512     {
1513       this.annotation.addElement(annotation);
1514     }
1515     annotation.setSequenceRef(this);
1516   }
1517
1518   @Override
1519   public void removeAlignmentAnnotation(AlignmentAnnotation annotation)
1520   {
1521     if (this.annotation != null)
1522     {
1523       this.annotation.removeElement(annotation);
1524       if (this.annotation.size() == 0)
1525       {
1526         this.annotation = null;
1527       }
1528     }
1529   }
1530
1531   /**
1532    * test if this is a valid candidate for another sequence's dataset sequence.
1533    * 
1534    */
1535   private boolean isValidDatasetSequence()
1536   {
1537     if (datasetSequence != null)
1538     {
1539       return false;
1540     }
1541     for (int i = 0; i < sequence.length; i++)
1542     {
1543       if (jalview.util.Comparison.isGap(sequence[i]))
1544       {
1545         return false;
1546       }
1547     }
1548     return true;
1549   }
1550
1551   @Override
1552   public SequenceI deriveSequence()
1553   {
1554     Sequence seq = null;
1555     if (datasetSequence == null)
1556     {
1557       if (isValidDatasetSequence())
1558       {
1559         // Use this as dataset sequence
1560         seq = new Sequence(getName(), "", 1, -1);
1561         seq.setDatasetSequence(this);
1562         seq.initSeqFrom(this, getAnnotation());
1563         return seq;
1564       }
1565       else
1566       {
1567         // Create a new, valid dataset sequence
1568         createDatasetSequence();
1569       }
1570     }
1571     return new Sequence(this);
1572   }
1573
1574   private boolean _isNa;
1575
1576   private int _seqhash = 0;
1577
1578   /**
1579    * Answers false if the sequence is more than 85% nucleotide (ACGTU), else
1580    * true
1581    */
1582   @Override
1583   public boolean isProtein()
1584   {
1585     if (datasetSequence != null)
1586     {
1587       return datasetSequence.isProtein();
1588     }
1589     if (_seqhash != sequence.hashCode())
1590     {
1591       _seqhash = sequence.hashCode();
1592       _isNa = Comparison.isNucleotide(this);
1593     }
1594     return !_isNa;
1595   };
1596
1597   /*
1598    * (non-Javadoc)
1599    * 
1600    * @see jalview.datamodel.SequenceI#createDatasetSequence()
1601    */
1602   @Override
1603   public SequenceI createDatasetSequence()
1604   {
1605     if (datasetSequence == null)
1606     {
1607       Sequence dsseq = new Sequence(getName(),
1608               AlignSeq.extractGaps(jalview.util.Comparison.GapChars,
1609                       getSequenceAsString()),
1610               getStart(), getEnd());
1611
1612       datasetSequence = dsseq;
1613
1614       dsseq.setDescription(description);
1615       // move features and database references onto dataset sequence
1616       dsseq.sequenceFeatureStore = sequenceFeatureStore;
1617       sequenceFeatureStore = null;
1618       dsseq.dbrefs = dbrefs;
1619       dbrefs = null;
1620       // TODO: search and replace any references to this sequence with
1621       // references to the dataset sequence in Mappings on dbref
1622       dsseq.pdbIds = pdbIds;
1623       pdbIds = null;
1624       datasetSequence.updatePDBIds();
1625       if (annotation != null)
1626       {
1627         // annotation is cloned rather than moved, to preserve what's currently
1628         // on the alignment
1629         for (AlignmentAnnotation aa : annotation)
1630         {
1631           AlignmentAnnotation _aa = new AlignmentAnnotation(aa);
1632           _aa.sequenceRef = datasetSequence;
1633           _aa.adjustForAlignment(); // uses annotation's own record of
1634                                     // sequence-column mapping
1635           datasetSequence.addAlignmentAnnotation(_aa);
1636         }
1637       }
1638     }
1639     return datasetSequence;
1640   }
1641
1642   /*
1643    * (non-Javadoc)
1644    * 
1645    * @see
1646    * jalview.datamodel.SequenceI#setAlignmentAnnotation(AlignmmentAnnotation[]
1647    * annotations)
1648    */
1649   @Override
1650   public void setAlignmentAnnotation(AlignmentAnnotation[] annotations)
1651   {
1652     if (annotation != null)
1653     {
1654       annotation.removeAllElements();
1655     }
1656     if (annotations != null)
1657     {
1658       for (int i = 0; i < annotations.length; i++)
1659       {
1660         if (annotations[i] != null)
1661         {
1662           addAlignmentAnnotation(annotations[i]);
1663         }
1664       }
1665     }
1666   }
1667
1668   @Override
1669   public AlignmentAnnotation[] getAnnotation(String label)
1670   {
1671     if (annotation == null || annotation.size() == 0)
1672     {
1673       return null;
1674     }
1675
1676     Vector<AlignmentAnnotation> subset = new Vector<>();
1677     Enumeration<AlignmentAnnotation> e = annotation.elements();
1678     while (e.hasMoreElements())
1679     {
1680       AlignmentAnnotation ann = e.nextElement();
1681       if (ann.label != null && ann.label.equals(label))
1682       {
1683         subset.addElement(ann);
1684       }
1685     }
1686     if (subset.size() == 0)
1687     {
1688       return null;
1689     }
1690     AlignmentAnnotation[] anns = new AlignmentAnnotation[subset.size()];
1691     int i = 0;
1692     e = subset.elements();
1693     while (e.hasMoreElements())
1694     {
1695       anns[i++] = e.nextElement();
1696     }
1697     subset.removeAllElements();
1698     return anns;
1699   }
1700
1701   @Override
1702   public boolean updatePDBIds()
1703   {
1704     if (datasetSequence != null)
1705     {
1706       // TODO: could merge DBRefs
1707       return datasetSequence.updatePDBIds();
1708     }
1709     if (dbrefs == null || dbrefs.length == 0)
1710     {
1711       return false;
1712     }
1713     boolean added = false;
1714     for (DBRefEntry dbr : dbrefs)
1715     {
1716       if (DBRefSource.PDB.equals(dbr.getSource()))
1717       {
1718         /*
1719          * 'Add' any PDB dbrefs as a PDBEntry - add is only performed if the
1720          * PDB id is not already present in a 'matching' PDBEntry
1721          * Constructor parses out a chain code if appended to the accession id
1722          * (a fudge used to 'store' the chain code in the DBRef)
1723          */
1724         PDBEntry pdbe = new PDBEntry(dbr);
1725         added |= addPDBId(pdbe);
1726       }
1727     }
1728     return added;
1729   }
1730
1731   @Override
1732   public void transferAnnotation(SequenceI entry, Mapping mp)
1733   {
1734     if (datasetSequence != null)
1735     {
1736       datasetSequence.transferAnnotation(entry, mp);
1737       return;
1738     }
1739     if (entry.getDatasetSequence() != null)
1740     {
1741       transferAnnotation(entry.getDatasetSequence(), mp);
1742       return;
1743     }
1744     // transfer any new features from entry onto sequence
1745     if (entry.getSequenceFeatures() != null)
1746     {
1747
1748       List<SequenceFeature> sfs = entry.getSequenceFeatures();
1749       for (SequenceFeature feature : sfs)
1750       {
1751        SequenceFeature sf[] = (mp != null) ? mp.locateFeature(feature)
1752                 : new SequenceFeature[] { new SequenceFeature(feature) };
1753         if (sf != null)
1754         {
1755           for (int sfi = 0; sfi < sf.length; sfi++)
1756           {
1757             addSequenceFeature(sf[sfi]);
1758           }
1759         }
1760       }
1761     }
1762
1763     // transfer PDB entries
1764     if (entry.getAllPDBEntries() != null)
1765     {
1766       Enumeration<PDBEntry> e = entry.getAllPDBEntries().elements();
1767       while (e.hasMoreElements())
1768       {
1769         PDBEntry pdb = e.nextElement();
1770         addPDBId(pdb);
1771       }
1772     }
1773     // transfer database references
1774     DBRefEntry[] entryRefs = entry.getDBRefs();
1775     if (entryRefs != null)
1776     {
1777       for (int r = 0; r < entryRefs.length; r++)
1778       {
1779         DBRefEntry newref = new DBRefEntry(entryRefs[r]);
1780         if (newref.getMap() != null && mp != null)
1781         {
1782           // remap ref using our local mapping
1783         }
1784         // we also assume all version string setting is done by dbSourceProxy
1785         /*
1786          * if (!newref.getSource().equalsIgnoreCase(dbSource)) {
1787          * newref.setSource(dbSource); }
1788          */
1789         addDBRef(newref);
1790       }
1791     }
1792   }
1793
1794   @Override
1795   public void setRNA(RNA r)
1796   {
1797     rna = r;
1798   }
1799
1800   @Override
1801   public RNA getRNA()
1802   {
1803     return rna;
1804   }
1805
1806   @Override
1807   public List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
1808           String label)
1809   {
1810     List<AlignmentAnnotation> result = new ArrayList<>();
1811     if (this.annotation != null)
1812     {
1813       for (AlignmentAnnotation ann : annotation)
1814       {
1815         String id = ann.getCalcId();
1816         if (id != null && id.equals(calcId)
1817                 && ann.label != null && ann.label.equals(label))
1818         {
1819           result.add(ann);
1820         }
1821       }
1822     }
1823     return result;
1824   }
1825
1826   @Override
1827   public String toString()
1828   {
1829     return getDisplayId(false);
1830   }
1831
1832   @Override
1833   public PDBEntry getPDBEntry(String pdbIdStr)
1834   {
1835     if (getDatasetSequence() != null)
1836     {
1837       return getDatasetSequence().getPDBEntry(pdbIdStr);
1838     }
1839     if (pdbIds == null)
1840     {
1841       return null;
1842     }
1843     List<PDBEntry> entries = getAllPDBEntries();
1844     for (PDBEntry entry : entries)
1845     {
1846       if (entry.getId().equalsIgnoreCase(pdbIdStr))
1847       {
1848         return entry;
1849       }
1850     }
1851     return null;
1852   }
1853
1854   @Override
1855   public List<DBRefEntry> getPrimaryDBRefs()
1856   {
1857     if (datasetSequence != null)
1858     {
1859       return datasetSequence.getPrimaryDBRefs();
1860     }
1861     if (dbrefs == null || dbrefs.length == 0)
1862     {
1863       return Collections.emptyList();
1864     }
1865     synchronized (dbrefs)
1866     {
1867       List<DBRefEntry> primaries = new ArrayList<>();
1868       DBRefEntry[] tmp = new DBRefEntry[1];
1869       for (DBRefEntry ref : dbrefs)
1870       {
1871         if (!ref.isPrimaryCandidate())
1872         {
1873           continue;
1874         }
1875         if (ref.hasMap())
1876         {
1877           MapList mp = ref.getMap().getMap();
1878           if (mp.getFromLowest() > start || mp.getFromHighest() < end)
1879           {
1880             // map only involves a subsequence, so cannot be primary
1881             continue;
1882           }
1883         }
1884         // whilst it looks like it is a primary ref, we also sanity check type
1885         if (DBRefUtils.getCanonicalName(DBRefSource.PDB)
1886                 .equals(DBRefUtils.getCanonicalName(ref.getSource())))
1887         {
1888           // PDB dbrefs imply there should be a PDBEntry associated
1889           // TODO: tighten PDB dbrefs
1890           // formally imply Jalview has actually downloaded and
1891           // parsed the pdb file. That means there should be a cached file
1892           // handle on the PDBEntry, and a real mapping between sequence and
1893           // extracted sequence from PDB file
1894           PDBEntry pdbentry = getPDBEntry(ref.getAccessionId());
1895           if (pdbentry != null && pdbentry.getFile() != null)
1896           {
1897             primaries.add(ref);
1898           }
1899           continue;
1900         }
1901         // check standard protein or dna sources
1902         tmp[0] = ref;
1903         DBRefEntry[] res = DBRefUtils.selectDbRefs(!isProtein(), tmp);
1904         if (res != null && res[0] == tmp[0])
1905         {
1906           primaries.add(ref);
1907           continue;
1908         }
1909       }
1910       return primaries;
1911     }
1912   }
1913
1914   @Override
1915   public HiddenMarkovModel getHMM()
1916   {
1917     return hmm;
1918   }
1919
1920   @Override
1921   public void setHMM(HiddenMarkovModel hmm)
1922   {
1923     this.hmm = hmm;
1924   }
1925
1926   @Override
1927   public boolean hasHMMAnnotation()
1928   {
1929     if (this.annotation == null) {
1930       return false;
1931     }
1932     for (AlignmentAnnotation ann : annotation)
1933     {
1934       if (InformationThread.HMM_CALC_ID.equals(ann.getCalcId()))
1935       {
1936         return true;
1937       }
1938     }
1939     return false;
1940   }
1941
1942   /**
1943    * {@inheritDoc}
1944    */
1945   @Override
1946   public List<SequenceFeature> findFeatures(int fromColumn, int toColumn,
1947           String... types)
1948   {
1949     int startPos = findPosition(fromColumn - 1); // convert base 1 to base 0
1950     int endPos = fromColumn == toColumn ? startPos
1951             : findPosition(toColumn - 1);
1952
1953     List<SequenceFeature> result = getFeatures().findFeatures(startPos,
1954             endPos, types);
1955
1956     /*
1957      * if end column is gapped, endPos may be to the right, 
1958      * and we may have included adjacent or enclosing features;
1959      * remove any that are not enclosing, non-contact features
1960      */
1961     boolean endColumnIsGapped = toColumn > 0 && toColumn <= sequence.length
1962             && Comparison.isGap(sequence[toColumn - 1]);
1963     if (endPos > this.end || endColumnIsGapped)
1964     {
1965       ListIterator<SequenceFeature> it = result.listIterator();
1966       while (it.hasNext())
1967       {
1968         SequenceFeature sf = it.next();
1969         int sfBegin = sf.getBegin();
1970         int sfEnd = sf.getEnd();
1971         int featureStartColumn = findIndex(sfBegin);
1972         if (featureStartColumn > toColumn)
1973         {
1974           it.remove();
1975         }
1976         else if (featureStartColumn < fromColumn)
1977         {
1978           int featureEndColumn = sfEnd == sfBegin ? featureStartColumn
1979                   : findIndex(sfEnd);
1980           if (featureEndColumn < fromColumn)
1981           {
1982             it.remove();
1983           }
1984           else if (featureEndColumn > toColumn && sf.isContactFeature())
1985           {
1986             /*
1987              * remove an enclosing feature if it is a contact feature
1988              */
1989             it.remove();
1990           }
1991         }
1992       }
1993     }
1994
1995     return result;
1996   }
1997
1998   /**
1999    * Invalidates any stale cursors (forcing recalculation) by incrementing the
2000    * token that has to match the one presented by the cursor
2001    */
2002   @Override
2003   public void sequenceChanged()
2004   {
2005     changeCount++;
2006   }
2007
2008   /**
2009    * {@inheritDoc}
2010    */
2011   @Override
2012   public int replace(char c1, char c2)
2013   {
2014     if (c1 == c2)
2015     {
2016       return 0;
2017     }
2018     int count = 0;
2019     synchronized (sequence)
2020     {
2021       for (int c = 0; c < sequence.length; c++)
2022       {
2023         if (sequence[c] == c1)
2024         {
2025           sequence[c] = c2;
2026           count++;
2027         }
2028       }
2029     }
2030     if (count > 0)
2031     {
2032       sequenceChanged();
2033     }
2034
2035     return count;
2036   }
2037
2038   @Override
2039   public String getSequenceStringFromIterator(Iterator<int[]> it)
2040   {
2041     StringBuilder newSequence = new StringBuilder();
2042     while (it.hasNext())
2043     {
2044       int[] block = it.next();
2045       if (it.hasNext())
2046       {
2047         newSequence.append(getSequence(block[0], block[1] + 1));
2048       }
2049       else
2050       {
2051         newSequence.append(getSequence(block[0], block[1]));
2052       }
2053     }
2054
2055     return newSequence.toString();
2056   }
2057
2058   @Override
2059   public int firstResidueOutsideIterator(Iterator<int[]> regions)
2060   {
2061     int start = 0;
2062
2063     if (!regions.hasNext())
2064     {
2065       return findIndex(getStart()) - 1;
2066     }
2067
2068     // Simply walk along the sequence whilst watching for region
2069     // boundaries
2070     int hideStart = getLength();
2071     int hideEnd = -1;
2072     boolean foundStart = false;
2073
2074     // step through the non-gapped positions of the sequence
2075     for (int i = getStart(); i <= getEnd() && (!foundStart); i++)
2076     {
2077       // get alignment position of this residue in the sequence
2078       int p = findIndex(i) - 1;
2079
2080       // update region start/end
2081       while (hideEnd < p && regions.hasNext())
2082       {
2083         int[] region = regions.next();
2084         hideStart = region[0];
2085         hideEnd = region[1];
2086       }
2087       if (hideEnd < p)
2088       {
2089         hideStart = getLength();
2090       }
2091       // update boundary for sequence
2092       if (p < hideStart)
2093       {
2094         start = p;
2095         foundStart = true;
2096       }
2097     }
2098
2099     if (foundStart)
2100     {
2101       return start;
2102     }
2103     // otherwise, sequence was completely hidden
2104     return 0;
2105   }
2106
2107   @Override
2108   public boolean hasHMMProfile()
2109   {
2110     return hmm != null;
2111   }
2112 }