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