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