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